Lab 73 — Treinar no SageMaker AI com experimento rastreável
O problema, e a empresa que o tem
A Cadência já resolveu o schema do dado (L65), o contrato de valor que protege o modelo de reposição automática de mudança silenciosa de unidade (L70), e passou a calcular cada feature de preço normalizado uma única vez, para treino e para inferência, com o Feature Store do L72. O modelo em produção hoje continua sendo o mesmo modelo de sempre — 40 lojistas, 310 SKUs, recomendação de reposição semanal.
Esse modelo específico, o que está servindo agora, foi treinado há três meses por um cientista de dados num notebook Jupyter local, antes de o Feature Store existir. Ele exportou um CSV do Athena, ajustou hiperparâmetro célula por célula até o resultado parecer bom, salvou um model.tar.gz num bucket S3 e avisou o time de plataforma para apontar o endpoint de inferência para aquele arquivo. Funcionou. Ninguém mais mexeu nisso.
Agora uma correção no ruleset do L70 mudou a distribuição de preço que entra no treino, e o time de dados precisa decidir: o modelo atual ainda é bom sobre o dado corrigido, ou precisa retreinar? Para responder, alguém teria de reproduzir o treino original e comparar. E aí aparece o problema: nem o próprio cientista que treinou consegue. O notebook ainda existe, mas as células foram reexecutadas fora de ordem dezenas de vezes desde então — o hiperparâmetro final não está escrito em lugar nenhum, o CSV exportado já não corresponde a nenhuma consulta reproduzível no Athena de hoje, e a seed aleatória do framework nunca foi fixada explicitamente.
O que este laboratório NÃO é
Não é sobre calcular a feature certa — o L72 já resolve que o treino e a inferência usam exatamente o mesmo cálculo. Não é sobre escolher entre regra e modelo — isso é o L71. E não é sobre COMO servir o modelo depois de treinado, nem sobre aprovar e promover uma versão — isso é o L74 e o L75, que dependem deste. Este módulo resolve uma coisa: dado o mesmo commit, o mesmo dado e o mesmo hiperparâmetro, alguém consegue reproduzir a métrica do treino que está em produção — hoje ninguém consegue, nem quem treinou.
O que você vai conseguir fazer
Objetivos verificáveis: cada um se prova com um comando ou uma consulta na seção de implantação.
- Explicar por que ter o Feature Store do L72 não resolve reprodutibilidade do treino — são dois problemas complementares, não o mesmo.
- Configurar um SageMaker Training Job com instâncias Spot e checkpoint periódico, sobrevivendo a uma interrupção sem perder o progresso.
- Registrar cada execução como um Run do SageMaker Experiments, com commit do código, hash do snapshot de dado, hiperparâmetro completo (incluindo seed) e métrica final.
- Justificar por que reprodutibilidade de métrica em treino distribuído é 'dentro de tolerância', não bit a bit — e o que isso muda na prova de reprodutibilidade.
- Escrever a policy IAM mínima do papel de execução, restrita ao prefixo de dado que o job precisa ler e ao prefixo de artefato que ele precisa escrever.
- Reexecutar um treino a partir de um Run salvo — mesmo commit, mesmo hash de dado, mesmo hiperparâmetro — e comparar a métrica nova com a original dentro de uma tolerância declarada.
- Diagnosticar, a partir dos hiperparâmetros de dois Runs, por que uma reprodução divergiu além da tolerância.
- Provar com número: qual a diferença percentual entre a métrica do Run original e a da reprodução, e se ela fica dentro do limiar aceito.
O que a certificação cobra disto
| Conceito | Certificação | Como aparece aqui | O que dominar |
|---|---|---|---|
| SageMaker Training Job e Estimator | MLA-C01 | job configurado com instance_count > 1, use_spot_instances e checkpoint_s3_uri | diferença entre instância On-Demand e Spot gerenciada pelo SageMaker, e o papel exato do checkpoint na retomada automática |
| SageMaker Experiments (Experiment, Trial, Run) | MLA-C01 | cada execução do job vira um Run com hiperparâmetro, commit, hash de dado e métrica, consultável por API | o que o SDK de Experiments registra por conta própria (duração, instância, imagem) e o que precisa de log manual (commit, hash de dado, seed) |
| Reprodutibilidade como prática de MLOps | MLA-C01 | commit, dado e hiperparâmetro versionados juntos, nunca 'estava no notebook' | por que determinismo BIT A BIT é diferente de determinismo de MÉTRICA em treino multi-thread de CPU |
| Hiperparâmetro como decisão registrada, não valor solto | MLA-C01, AIF-C01 | hiperparâmetro passado como argumento estruturado do Estimator, nunca hardcoded no meio do script | diferença entre hiperparâmetro (decisão humana, ex.: learning rate) e parâmetro aprendido (peso do modelo, saída do treino) |
| Custo de treino: Spot vs. On-Demand | MLA-C01, MLS-C01 | Estimator com use_spot_instances=True, max_wait e max_run configurados explicitamente | por que Spot exige checkpoint para ser seguro, e o que acontece com o job se max_wait expira antes da conclusão |
Requisitos, e como cada um muda o desenho
Requisito que não muda uma linha de configuração é intenção, não requisito. A terceira coluna é onde cada um deixou marca no desenho.
| Requisito | Valor declarado | O que ele decide no desenho |
|---|---|---|
| Ninguém reproduz o modelo que está em produção | mesmo commit + mesmo hash de snapshot + mesmo hiperparâmetro produzem métrica dentro de uma tolerância numérica declarada | obriga que CADA execução do Training Job vire um Run do SageMaker Experiments, registrado ANTES de o job terminar, não reconstruído depois de memória |
| Treino não pode perder progresso quando a instância Spot é interrompida | retomada automática a partir do último checkpoint, sem reprocessar do zero | exige checkpoint_s3_uri configurado no Estimator e lógica no script de treino que procura um checkpoint antes de inicializar os pesos do zero |
| O dado de treino tem de ser exatamente o mesmo entre duas execuções do mesmo commit | timestamp de corte (as-of) do join ponto-no-tempo do L72 travado, e hash do dataset resultante da extração, nunca 'a versão mais recente' | o Run recusa rodar sem os dois identificadores — o as-of do `point_in_time_accurate_join()` e o hash do arquivo extraído — e ambos viram parte do que o Experiments registra |
| A seed aleatória tem de ser fixada e registrada, não deixada no default | um único valor de seed aplicado a numpy, ao framework de treino e, quando existir, à biblioteca de CUDA | seed vira hiperparâmetro versionado no Run, com valor explícito — nunca um número que só existe dentro da sessão do notebook |
| Custo de treino contido, mesmo com múltiplas tentativas de reprodução | instância Spot com checkpoint, não instância On-Demand ociosa esperando aprovação humana | Estimator com use_spot_instances=True, max_wait e max_run — não a configuração padrão do SDK |
| Papel de execução restrito ao que o job realmente usa | leitura só do prefixo do snapshot do Feature Store, escrita só nos prefixos de artefato e checkpoint | policy IAM por prefixo específico, chave KMS dedicada à criptografia do artefato e do checkpoint — nunca `Resource: "*"` |
Arquitetura mínima: o notebook que treinou o modelo que está em produção
Este é o desenho que gerou o modelo que a Cadência usa hoje, e ele é legítimo como ponto de partida: um cientista de dados treinando localmente é como praticamente todo mundo começa. O defeito não está em treinar num notebook — está em nada, além da memória de quem escreveu o código, sobreviver depois que o job termina.
- → hiperparâmetro passado à mão como argumento da chamada, nunca versionado
- → join ponto-no-tempo sem as-of fixo, então lê o instante da chamada, não um corte travado
- → grava o artefato final, sobrescrevendo o caminho do treino anterior
- Fora da AWS
- Conceito de arquitetura
- IA e machine learning
- Armazenamento
Este desenho produz um modelo que serve em produção hoje — e nenhuma peça dele sobrevive além da execução. Percorra os passos e repare onde a informação necessária para reproduzir o resultado desaparece.
- O cientista escreve o script no notebook local, sem exigência de commit antes de rodar. As células são editadas e reexecutadas fora de ordem ao longo de semanas. Não há gate nenhum entre 'terminei de ajustar' e 'rodei o treino que virou produção'.
- O job chama o join ponto-no-tempo do Feature Store sem travar um as-of. O L72 garante que o CÁLCULO da feature é o mesmo no treino e na inferência — mas 'o mesmo cálculo' não é 'o mesmo dado no tempo'. Sem um as-of explícito passado a point_in_time_accurate_join(), cada execução reconstrói o dataset a partir de 'agora', e duas chamadas em dias diferentes podem devolver linhas diferentes.
- O treino roda numa única instância; a seed aleatória do framework nunca é fixada explicitamente. Sem instance_count > 1 e sem seed declarada, cada execução inicializa pesos e embaralha lotes de um jeito ligeiramente diferente — mesmo sobre o dado idêntico.
- O job grava o artefato final, sobrescrevendo o mesmo caminho do treino anterior. model.tar.gz vira o único registro físico do resultado. Não existe metadado estruturado anexado a ele — nem commit, nem hiperparâmetro, nem hash de dado.
- Nenhum registro liga este arquivo ao commit, ao dado ou ao hiperparâmetro que o geraram. É exatamente esse vazio que aparece três meses depois, quando alguém precisa responder 'este modelo ainda é bom sobre o dado corrigido?' e não tem como reproduzir o treino original para comparar.
# Reproduz o cenário: dois treinos do MESMO notebook, dado supostamente igual,
# hiperparametro so na cabeca de quem escreveu -- e a metrica diverge.
# 1) Primeiro treino, ha tres meses (simulado aqui como execucao A)
python treinar_local.py --learning-rate 0.03 --arvores 400
# saida: rmse_validacao=18.42 -- ninguem anotou esses dois numeros em lugar nenhum
# 2) Hoje, tentando reproduzir de memoria (execucao B)
python treinar_local.py --learning-rate 0.05 --arvores 350
# saida: rmse_validacao=21.07 -- diferente, e ninguem sabe dizer se e o
# hiperparametro, o dado, ou a seed que mudou, porque nenhum dos tres foi
# registrado na primeira execucao
# 3) Confirma que nao ha NENHUM metadado de proveniencia no artefato publicado
aws s3api head-object --bucket cadencia-ml --key modelos/modelo-reposicao.tar.gz \
--query 'Metadata'
# Esperado: {} -- objeto vazio. So existe o binario, sem commit, sem dado, sem hiperparametro.
O job SUCCEEDED não é a informação que falta — a proveniência é
As duas execuções terminam sem erro nenhum: JobRunState fica SUCCEEDED nas duas, e cada uma produz um model.tar.gz válido. O problema nunca aparece como falha — aparece como duas métricas diferentes sem ninguém saber dizer por quê, porque a arquitetura mínima nunca registrou o que teria explicado a diferença: hiperparâmetro exato, hash do dado exato, seed exata.
Arquitetura para produção
Cada peça nova abaixo rastreia a uma linha da tabela de requisitos. A troca não é 'acrescentar um log' ao desenho anterior — é substituir 'terminou sem erro' por 'terminou com proveniência completa', e 'na cabeça de quem treinou' por 'consultável por API'.
- → commit hash como argumento obrigatório do job, não anotação solta
- → autoriza a leitura do dataset extraído e a escrita nos prefixos de artefato e checkpoint
- → chave usada para criptografar cada objeto gravado por este job
- → entrega o dataset reconciliado pelo as-of travado, não pelo instante da chamada
- → grava o estado do treino a cada N passos configurados
- → retomada automática do último checkpoint quando uma instância Spot é interrompida
- → registra commit, hash de dado, hiperparâmetro completo e métrica final ao terminar
- → grava o artefato nomeado pelo run_id, sem sobrescrever execuções anteriores
- → envia log e métrica de utilização durante toda a execução
- Fora da AWS
- Conceito de arquitetura
- Segurança e identidade
- IA e machine learning
- Armazenamento
- Gestão e governança
A diferença não é uma caixa a mais: o papel de execução e a chave KMS autorizam o job antes de qualquer leitura, o treino agora é distribuído em instâncias Spot com checkpoint recuperável, e cada execução termina registrada num Run do Experiments — com commit, hash de dado, hiperparâmetro e métrica. Percorra os passos: cada peça nova resolve exatamente um requisito da seção anterior.
- O papel de execução e a chave KMS autorizam o job antes de qualquer leitura. iam e kms não são adorno: sem a policy escopada ao prefixo certo e sem a chave dedicada, o job simplesmente não sobe — a autorização vem antes do treino, não depois dele.
- O commit do script vira argumento obrigatório do job, não anotação solta. O job recusa rodar sem um commit hash associado. Isso fecha a lacuna da arquitetura mínima: não existe mais 'reexecutei o notebook de memória' como caminho válido.
- O job chama o join ponto-no-tempo com um as-of travado, e hasheia o resultado. Diferente da mínima, que lia 'o instante da chamada', aqui o as-of passado a point_in_time_accurate_join() é parte do contrato do job, e o hash do dataset extraído fica registrado — a mesma chamada, em qualquer dia, reconstrói o mesmo dado, e o hash prova isso.
- Múltiplas instâncias Spot treinam em paralelo, salvando checkpoint periódico. instance_count > 1 distribui o treino, e use_spot_instances=True reduz o custo — a um preço: a AWS pode recuperar a instância a qualquer momento, com aviso de 2 minutos.
- Se uma instância Spot é interrompida, o job retoma automaticamente do último checkpoint. checkpoint_s3_uri é o que torna Spot seguro de usar: o script de treino procura um checkpoint existente antes de inicializar pesos do zero, e continua dali.
- Ao terminar, o job registra commit, hash de dado, hiperparâmetro e métrica num Run. Isso é o que a arquitetura mínima nunca fez: a chamada ao SDK de Experiments acontece DENTRO do script de treino, antes de o job encerrar — não é um passo manual que alguém pode esquecer.
- O artefato final é gravado por run_id, e logs vão ao CloudWatch. Cada execução tem seu próprio caminho de artefato — nada é sobrescrito. Quem precisar investigar um treino de três meses atrás encontra o artefato exato, não a versão mais recente por cima.
A diferença estrutural em relação à mínima não é 'ligar o Experiments': é que a proveniência do treino deixou de depender da memória de uma pessoa. Cada Run existe independente de quem o criou ainda estar no time, e é consultável pela API meses depois — que é exatamente o cenário que expôs o problema deste módulo.
O ganho que só aparece três meses depois
No dia em que o treino roda, registrar commit e hiperparâmetro parece burocracia sem retorno imediato — o modelo já funciona, para quê anotar mais? O retorno aparece exatamente quando alguém, meses depois, precisa responder se o modelo em produção ainda é válido: com o Run, a resposta é uma consulta à API; sem ele, é uma investigação arqueológica que pode nem chegar a uma resposta.
O caminho do treino, ponta a ponta
Os campos que o Run registra não são acidente: commit_hash e snapshot_hash são o que transforma 'rodei de novo' em 'rodei exatamente a mesma coisa'. É observável consultando o Run pela API, e a seção de provas faz exatamente isso.
O que fica registrado num Run do SageMaker Experiments ao final do treino. Estes campos sao escolha DESTE modulo -- o SDK registra duracao, instancia e imagem automaticamente; commit_hash, snapshot_hash e as chaves de hiperparametro sao logados explicitamente pelo script.
{
"ExperimentName": "cadencia-modelo-reposicao",
"TrialName": "treino-2026-08-08-01",
"RunName": "run-a1b2c3d4",
"Parameters": {
"commit_hash": "e4f1a02",
"snapshot_hash": "fs-2026-08-05T23-00-00Z-sha256-7d9c1e",
"learning_rate": 0.05,
"n_estimators": 400,
"max_depth": 6,
"seed": 42,
"instance_type": "ml.m5.2xlarge",
"instance_count": 2,
"use_spot_instances": true
},
"Metrics": {
"rmse_validacao": 18.41,
"tempo_treino_segundos": 1847
}
}
Duração e instância vêm de graça; commit e hash de dado, não
O SDK de Experiments captura sozinho a duração do job, o tipo de instância e a imagem de container usada — isso vem do próprio CreateTrainingJob, sem código extra. commit_hash, snapshot_hash e cada hiperparâmetro só aparecem no Run porque o script chama explicitamente `run.log_parameters(...)` antes de terminar — esquecer essa chamada produz um Run tecnicamente registrado e pedagogicamente inútil.
As decisões, e o que se perde em cada uma
📋 A Cadência precisa reexecutar o treino do modelo de reposição para investigar se ele continua bom sobre o dado corrigido pelo ruleset do L70 — mas o notebook original não roda mais como rodava: hiperparâmetro perdido, dado de três meses atrás sem um snapshot correspondente, seed nunca fixada.
Resolve as três lacunas — hiperparâmetro, dado, seed — usando peças que já existem no ecossistema (Training Job, Feature Store do L72, Experiments), sem inventar um serviço de rastreamento paralelo. O checkpoint em Spot resolve resiliência e custo ao mesmo tempo: treino mais barato, sem risco de perder progresso na interrupção.
Alt: Continuar treinando em notebook local, só com mais disciplina de anotação manual — depende de disciplina humana constante — exatamente o que já falhou uma vez. Sem uma API que force o registro, a próxima pessoa reproduz o mesmo buraco de proveniência.
Alt: Guardar hiperparâmetro e métrica numa planilha compartilhada — a planilha não vincula automaticamente ao job de fato executado, e diverge do real assim que alguém edita a célula errada ou esquece de atualizar depois de um treino.
Alt: Confiar no nome do arquivo do artefato para saber o que é (modelo_v2_final_bom.tar.gz) — nome de arquivo não é metadado estruturado — não tem hiperparâmetro, não tem hash de dado, e 'final' já apareceu em nome de arquivo que não era o final.
Alt: Treinar sempre em instância On-Demand para evitar a complexidade de checkpoint de Spot — troca o problema de resiliência por custo mais alto sem necessidade — o problema real deste módulo, rastreabilidade, continua sem solução nenhuma, só fica mais caro rodar de novo.
| Decisão | Escolha | Alternativa considerada | Motivo | O que se perde |
|---|---|---|---|---|
| Onde registrar hiperparâmetro e métrica | SageMaker Experiments (Run) | planilha ou README mantido manualmente | consultável por API, vinculado ao ARN do job que de fato rodou, não a uma cópia que alguém pode esquecer de atualizar | sintaxe própria do SDK de Experiments que o time precisa aprender |
| Como sobreviver à interrupção da instância Spot | checkpoint periódico em S3 com retomada automática | instância On-Demand, sem Spot | Spot custa uma fração do On-Demand para o mesmo treino, e o checkpoint torna a interrupção um custo de minutos, não de horas | lógica de checkpoint e retomada a mais dentro do script de treino |
| Como travar a versão do dado de treino | hash do snapshot do Feature Store registrado no Run | sempre ler 'a versão mais recente' da tabela | garante que o MESMO commit, rodado duas vezes, treina sobre o MESMO dado — pré-requisito para qualquer comparação de métrica fazer sentido | exige que o Feature Store do L72 exponha um identificador de snapshot, não só a leitura corrente |
| Onde fixar a seed aleatória | argumento de hiperparâmetro versionado, aplicado a numpy, ao framework e à CUDA quando existir | deixar o valor default de cada biblioteca | o default muda entre versões de biblioteca e nunca fica registrado em lugar nenhum — fixar custa uma linha de código | mais um valor que o script precisa setar explicitamente em cada biblioteca usada |
A dívida que este módulo cria, e que ele não paga
LightGBM com mais de uma thread usa soma em ponto flutuante cuja ORDEM de acumulação entre threads não é garantida — o histograma de cada split pode fechar com um arredondamento ligeiramente diferente entre execuções, mesmo com `random_state` idêntico. A prova de reprodutibilidade deste módulo é DENTRO DE TOLERÂNCIA numérica, não bit idêntica. Forçar determinismo bit a bit exige `deterministic=True` mais `force_row_wise=True` — trade-off de velocidade que este módulo NÃO faz, porque não é o problema que ele resolve — o problema aqui é rastrear a PROVENIÊNCIA (commit, dado, hiperparâmetro), que é exatamente reprodutível, mesmo quando o último dígito da métrica varia.
Construir: o papel de execução e a chave que criptografa o artefato
A escolha mais consequente deste bloco não é a chave KMS em si — é o escopo da policy IAM. Ler o bucket inteiro do Feature Store 'porque é mais simples' é o mesmo erro que o L65 já ensinou a evitar em outro contexto: escopo largo hoje é auditoria dolorosa amanhã.
# treino.tf -- papel de execucao, chave KMS e bucket de artefatos
resource "aws_kms_key" "treino_reposicao" {
description = "Criptografa artefato e checkpoint do treino do modelo de reposicao"
deletion_window_in_days = 30
enable_key_rotation = true
}
resource "aws_kms_alias" "treino_reposicao" {
name = "alias/${var.projeto}-treino-reposicao"
target_key_id = aws_kms_key.treino_reposicao.key_id
}
resource "aws_s3_bucket_versioning" "modelos" {
bucket = var.bucket_ml
versioning_configuration {
status = "Enabled"
}
}
resource "aws_iam_role" "treino_execucao" {
name = "${var.projeto}-sagemaker-treino-execucao"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "sagemaker.amazonaws.com" }
Action = "sts:AssumeRole"
}]
})
}
# Le SO o prefixo do snapshot do Feature Store -- nao o bucket inteiro.
data "aws_iam_policy_document" "le_snapshot" {
statement {
effect = "Allow"
actions = ["s3:GetObject", "s3:ListBucket"]
resources = [
"arn:aws:s3:::${var.bucket_feature_store}",
"arn:aws:s3:::${var.bucket_feature_store}/offline/produtos/*",
]
}
}
# Escreve SO nos prefixos de artefato e checkpoint deste job.
data "aws_iam_policy_document" "escreve_artefato" {
statement {
effect = "Allow"
actions = ["s3:PutObject", "s3:GetObject"]
resources = [
"arn:aws:s3:::${var.bucket_ml}/modelos/*",
"arn:aws:s3:::${var.bucket_ml}/checkpoints/*",
]
}
}
data "aws_iam_policy_document" "usa_kms" {
statement {
effect = "Allow"
actions = ["kms:Encrypt", "kms:Decrypt", "kms:GenerateDataKey"]
resources = [aws_kms_key.treino_reposicao.arn]
}
}
resource "aws_iam_role_policy" "treino_le_snapshot" {
role = aws_iam_role.treino_execucao.id
policy = data.aws_iam_policy_document.le_snapshot.json
}
resource "aws_iam_role_policy" "treino_escreve_artefato" {
role = aws_iam_role.treino_execucao.id
policy = data.aws_iam_policy_document.escreve_artefato.json
}
resource "aws_iam_role_policy" "treino_usa_kms" {
role = aws_iam_role.treino_execucao.id
policy = data.aws_iam_policy_document.usa_kms.json
}
output "papel_execucao_arn" {
value = aws_iam_role.treino_execucao.arn
}
O papel do job não é o papel de quem chama o job
aws_iam_role.treino_execucao é o papel que o SERVIÇO SageMaker assume para rodar o container de treino — equivalente à execution role do ECS que o L01 já distinguiu da task role. Quem CHAMA CreateTrainingJob (a pessoa, ou o serviço do .NET da seção seguinte) usa uma identidade própria, com permissão só de `sagemaker:CreateTrainingJob` — confundir as duas é dar ao chamador acesso direto ao S3 que só o job deveria ter.
Construir: o script que treina e se registra sozinho
A mudança em relação ao notebook original é cirúrgica em código, e decisiva em efeito: três chamadas a mais — fixar seed, procurar checkpoint, logar o Run — transformam um treino descartável num treino auditável.
# treinar_reposicao.py -- roda dentro do container do Training Job.
# Hiperparametro chega via argumento (SageMaker injeta como --nome valor);
# commit e hash do snapshot chegam do mesmo jeito, nunca hardcoded.
import argparse
import os
import random
import numpy as np
from sagemaker.experiments.run import Run, load_run
from lightgbm import LGBMRegressor
import joblib
def fixar_seed(seed: int) -> None:
# Um unico valor aplicado a TODAS as fontes de aleatoriedade que o
# script usa. Fixar so o numpy e deixar o framework de treino no
# default e o erro mais comum: parece reprodutivel e nao e.
random.seed(seed)
np.random.seed(seed)
os.environ["PYTHONHASHSEED"] = str(seed)
def buscar_checkpoint(diretorio: str):
# Se a instancia Spot foi interrompida e o job foi reiniciado, o
# SageMaker preserva o conteudo de --checkpoint-s3-uri no mesmo
# diretorio local. Comeca do zero SO se nao houver nada aqui.
caminho = os.path.join(diretorio, "modelo.checkpoint")
if os.path.exists(caminho):
print(f"checkpoint encontrado em {caminho}, retomando")
return joblib.load(caminho)
print("nenhum checkpoint -- treino comeca do zero")
return None
def main() -> None:
p = argparse.ArgumentParser()
p.add_argument("--commit-hash", required=True)
p.add_argument("--snapshot-hash", required=True)
p.add_argument("--learning-rate", type=float, required=True)
p.add_argument("--n-estimators", type=int, required=True)
p.add_argument("--max-depth", type=int, required=True)
p.add_argument("--seed", type=int, required=True)
p.add_argument("--checkpoint-dir", default="/opt/ml/checkpoints")
p.add_argument("--model-dir", default=os.environ.get("SM_MODEL_DIR"))
p.add_argument("--train", default=os.environ.get("SM_CHANNEL_TRAIN"))
args = p.parse_args()
fixar_seed(args.seed)
# dentro de um Training Job, load_run() acha o contexto do Run
# automaticamente -- nao precisa passar experiment_name na mao aqui.
with load_run() as run:
# Registra ANTES de treinar: se o job cair no meio, o Run ja tem
# a proveniencia completa, mesmo sem metrica final.
run.log_parameters({
"commit_hash": args.commit_hash,
"snapshot_hash": args.snapshot_hash,
"learning_rate": args.learning_rate,
"n_estimators": args.n_estimators,
"max_depth": args.max_depth,
"seed": args.seed,
})
x_treino, y_treino, x_val, y_val = carregar_snapshot(
args.train, args.snapshot_hash)
modelo = buscar_checkpoint(args.checkpoint_dir) or LGBMRegressor(
learning_rate=args.learning_rate,
n_estimators=args.n_estimators,
max_depth=args.max_depth,
random_state=args.seed,
)
modelo.fit(x_treino, y_treino)
joblib.dump(modelo, os.path.join(args.checkpoint_dir, "modelo.checkpoint"))
rmse = avaliar(modelo, x_val, y_val)
run.log_metric("rmse_validacao", rmse)
joblib.dump(modelo, os.path.join(args.model_dir, "model.joblib"))
print(f"rmse_validacao={rmse:.4f} commit={args.commit_hash} "
f"snapshot={args.snapshot_hash}")
if __name__ == "__main__":
main()
# disparar_treino.py -- roda FORA do container, monta o Estimator e
# inicia o job. E aqui que Spot, checkpoint e distribuicao sao configurados.
import subprocess
from sagemaker.sklearn.estimator import SKLearn
commit_hash = subprocess.check_output(
["git", "rev-parse", "--short", "HEAD"]).decode().strip()
# LightGBM roda em CPU, nao em GPU -- o container de scikit-learn do
# SageMaker cobre o essencial (joblib, numpy), e requirements.txt via
# `dependencies` instala o LightGBM em cima. Usar um container PyTorch
# aqui pagaria por GPU que o LightGBM nunca usa, e faltaria a biblioteca.
estimator = SKLearn(
entry_point="treinar_reposicao.py",
dependencies=["requirements.txt"], # so uma linha: lightgbm==4.3.0
role="arn:aws:iam::<conta>:role/cadencia-sagemaker-treino-execucao",
instance_count=2,
instance_type="ml.m5.2xlarge",
framework_version="1.2-1",
py_version="py3",
# Spot reduz custo, e SO e seguro por causa do checkpoint abaixo.
use_spot_instances=True,
max_wait=7200, # tempo total tolerado, incluindo espera por capacidade
max_run=5400, # tempo de treino em si
checkpoint_s3_uri="s3://cadencia-ml/checkpoints/",
output_path="s3://cadencia-ml/modelos/",
hyperparameters={
"commit-hash": commit_hash,
"snapshot-hash": "fs-2026-08-05T23-00-00Z-sha256-7d9c1e",
"learning-rate": 0.05,
"n-estimators": 400,
"max-depth": 6,
"seed": 42,
},
)
estimator.fit({"train": "s3://cadencia-ml/feature-store/offline/produtos/"})
print(f"job iniciado a partir do commit {commit_hash}")
Fixar a seed do numpy e esquecer a do framework produz reprodutibilidade falsa
`np.random.seed(args.seed)` sozinho NÃO fixa a inicialização de pesos do LightGBM/PyTorch nem o embaralhamento de lote do framework — cada biblioteca tem sua própria fonte de aleatoriedade, e algumas exigem seed própria (`random_state` no scikit-learn/LightGBM, `torch.manual_seed` no PyTorch). Um script que fixa só uma fonte parece determinístico no teste rápido e diverge silenciosamente na primeira reprodução real, meses depois.
Construir: reproduzir um Run a partir do serviço em .NET
O time de plataforma da Cadência expõe um endpoint interno que qualquer engenheiro usa para pedir 'reproduza o Run X' sem abrir o console do SageMaker. A parte que mais importa aqui não é iniciar o job de novo — é NÃO declarar sucesso só porque ele terminou.
// ReproduzirTreinoHandler.cs -- inicia um treino com o MESMO commit, dado
// e hiperparametro de um Run existente, e compara a metrica dentro de
// uma tolerancia -- nunca bit a bit, pelo motivo explicado na secao de decisoes.
using Amazon.SageMaker;
using Amazon.SageMaker.Model;
using Amazon.Lambda.Core;
namespace Cadencia.MLOps;
public record ReproduzirRun(string RunOriginalArn, double ToleranciaRelativa = 0.01);
public class ReproduzirTreinoHandler
{
private static readonly AmazonSageMakerClient _sagemaker = new();
private const string PapelExecucao =
"arn:aws:iam::<conta>:role/cadencia-sagemaker-treino-execucao";
public async Task<string> ReproduzirAsync(ReproduzirRun pedido, ILambdaContext contexto)
{
// 1. Le o Run original -- commit, hash de dado e hiperparametro
// completo, exatamente o que foi registrado no treino de tres
// meses atras.
var runOriginal = await ObterParametrosDoRunAsync(pedido.RunOriginalArn);
double metricaOriginal = runOriginal.Metricas["rmse_validacao"];
// 2. Inicia um NOVO job com os MESMOS parametros -- nao aprovado a
// aprovar novo hiperparametro aqui, so reproduzir o que existia.
var nomeJob = $"reproducao-{DateTime.UtcNow:yyyyMMddHHmmss}";
await _sagemaker.CreateTrainingJobAsync(new CreateTrainingJobRequest
{
TrainingJobName = nomeJob,
RoleArn = PapelExecucao,
HyperParameters = runOriginal.Hiperparametros,
EnableManagedSpotTraining = true,
StoppingCondition = new StoppingCondition { MaxWaitTimeInSeconds = 7200, MaxRuntimeInSeconds = 5400 },
CheckpointConfig = new CheckpointConfig { S3Uri = "s3://cadencia-ml/checkpoints/" },
// ... AlgorithmSpecification, ResourceConfig, InputDataConfig omitidos por brevidade
});
await AguardarConclusaoAsync(nomeJob, contexto);
// 3. NAO declara reproduzivel so porque o job terminou SUCCEEDED.
// Compara a metrica nova com a original, DENTRO DE TOLERANCIA --
// treino multi-thread em CPU nao garante bit a bit (secao de decisoes).
double metricaNova = await ObterMetricaFinalAsync(nomeJob);
double diferencaRelativa = Math.Abs(metricaNova - metricaOriginal) / metricaOriginal;
contexto.Logger.LogInformation(
$"original={metricaOriginal:F4} nova={metricaNova:F4} " +
$"diferenca_relativa={diferencaRelativa:P2} tolerancia={pedido.ToleranciaRelativa:P2}");
if (diferencaRelativa > pedido.ToleranciaRelativa)
{
throw new InvalidOperationException(
$"reproducao FALHOU: diferenca de {diferencaRelativa:P2} excede a " +
$"tolerancia de {pedido.ToleranciaRelativa:P2} -- confira se o " +
"commit, o snapshot e o hiperparametro realmente batem com o Run original");
}
return $"reproduzido: {nomeJob}, diferenca de {diferencaRelativa:P2} " +
$"dentro da tolerancia de {pedido.ToleranciaRelativa:P2}";
}
}
Um por cento de tolerância não é arbitrário — é o que a métrica da Cadência tolera
A tolerância de 1% relativo neste exemplo vem do que faz sentido para rmse_validacao de reposição de estoque, não de um padrão universal — outro modelo, outra métrica, pode exigir uma tolerância mais apertada ou mais larga. O que é fixo é o PRINCÍPIO: a prova de reprodutibilidade compara contra um limiar declarado explicitamente, nunca contra 'parece parecido'.
Implantar, e provar que o treino é reproduzível
Quatro provas. A terceira é a que mais gente pula, porque exige rodar o MESMO treino duas vezes de propósito só para comparar — mas é exatamente essa comparação que este módulo promete.
# provas.sh -- quatro medicoes; nenhuma conclusao vem de "o job rodou"
PROJETO=cadencia; BUCKET=cadencia-ml
# --- Prova 1: o Run registra os quatro campos obrigatorios ------------------
aws sagemaker describe-trial-component --trial-component-name run-a1b2c3d4 \
--query 'Parameters.{commit:commit_hash, snapshot:snapshot_hash, lr:learning_rate, seed:seed}'
# Esperado: os quatro campos preenchidos, nenhum vazio.
# --- Prova 2: interrupcao de Spot retoma do checkpoint, nao do zero ---------
aws sagemaker stop-training-job --training-job-name treino-2026-08-08-01
sleep 5
aws sagemaker create-training-job --training-job-name treino-2026-08-08-01-retomado \
--cli-input-json file://job-retomado.json
aws sagemaker describe-training-job --training-job-name treino-2026-08-08-01-retomado \
--query 'SecondaryStatusTransitions[?Status==`Downloading`].StatusMessage'
# Esperado: mensagem confirma leitura do checkpoint previo, nao inicializacao do zero.
# --- Prova 3: reproduzir o MESMO Run duas vezes e comparar a metrica --------
aws sagemaker create-training-job --cli-input-json file://reproducao-a.json
aws sagemaker create-training-job --cli-input-json file://reproducao-b.json
aws sagemaker wait training-job-completed-or-stopped --training-job-name reproducao-a
aws sagemaker wait training-job-completed-or-stopped --training-job-name reproducao-b
aws sagemaker describe-training-job --training-job-name reproducao-a \
--query 'FinalMetricDataList[?MetricName==`rmse_validacao`].Value' --output text
aws sagemaker describe-training-job --training-job-name reproducao-b \
--query 'FinalMetricDataList[?MetricName==`rmse_validacao`].Value' --output text
# Esperado: diferenca relativa entre os dois valores abaixo de 1%.
# --- Prova 4: papel de execucao nao alcanca nada fora do escopo -------------
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::<conta>:role/cadencia-sagemaker-treino-execucao \
--action-names s3:GetObject \
--resource-arns arn:aws:s3:::cadencia-lake/quarentena/produtos/dt=2026-07-19/arquivo.parquet
# Esperado: implicitDeny -- o papel do treino nao le o prefixo de quarentena do L70.
| Prova | Comando | Resultado que aprova | O que reprova, e o que significa |
|---|---|---|---|
| 1 · Run com os quatro campos | describe-trial-component sobre commit/snapshot/hiperparâmetro/seed | os quatro campos preenchidos, nenhum vazio | campo vazio indica que `run.log_parameters` não foi chamado, ou foi chamado antes de o valor existir |
| 2 · Retomada de checkpoint após interrupção | stop-training-job seguido de novo create-training-job apontando o mesmo checkpoint_s3_uri | mensagem de status confirma leitura de checkpoint prévio | job reiniciando do zero indica que `buscar_checkpoint` não está sendo chamado, ou o caminho do checkpoint está errado |
| 3 · Duas reproduções do mesmo Run, métrica comparada | dois jobs com os mesmos hiperparâmetros, `FinalMetricDataList` de cada um | diferença relativa abaixo de 1% | diferença acima do limiar indica hiperparâmetro faltando no registro, seed não propagada a alguma biblioteca, ou snapshot que mudou entre as duas execuções |
| 4 · Papel de execução restrito ao escopo declarado | iam simulate-principal-policy sobre um prefixo fora do escopo | implicitDeny para qualquer prefixo que não seja o snapshot do Feature Store nem os prefixos de artefato/checkpoint | allow inesperado indica policy escrita com `Resource: "*"` em algum statement, não por prefixo específico |
Quebrar de propósito: quatro maneiras de perder a reprodutibilidade
Um treino reproduzível não é um treino que rodou duas vezes com o mesmo número — é um treino cuja diferença, quando aparece, você consegue EXPLICAR. As quatro injeções abaixo produzem divergência de métrica por quatro causas diferentes, e a habilidade que este laboratório treina é separá-las.
| Falha injetada | Como injetar | Sintoma enganoso | Diagnóstico correto |
|---|---|---|---|
| Dado de origem mudou entre os dois Runs | Rodar o treino, ingerir um lote novo no Feature Store, rodar de novo com o MESMO código e o MESMO hiperparâmetro | A métrica muda. Como o código é idêntico e o commit é o mesmo, a conclusão imediata é "o treino não é determinístico" | O treino está perfeitamente determinístico — a ENTRADA mudou. É exatamente o defeito que trouxe este módulo à existência, reaparecendo com outra roupa. Por isso o Run registra o timestamp de corte do Feature Store, não só o nome do dataset: sem essa marca, "mesmo dataset" é uma afirmação que ninguém consegue verificar |
| Semente não fixada | Remover a fixação de seed do script e rodar duas vezes sobre o mesmo corte | Diferença pequena na métrica, na terceira ou quarta casa. Fácil de descartar como ruído de medição | É ruído real, mas ruído que você escolheu não controlar. Numa comparação entre modelo atual e candidato, uma diferença de terceira casa é frequentemente a diferença que decide a promoção — e sem seed fixa você não sabe se promoveu o modelo melhor ou o sorteio melhor. Fixar seed não torna o modelo bom; torna a COMPARAÇÃO honesta |
| Instância spot interrompida sem checkpoint | Configurar o job com `use_spot_instances=True` e `max_wait` curto, sem `checkpoint_s3_uri` | O job reinicia e termina mais tarde, com métrica final parecida. Custa mais tempo, e a leitura é "spot é instável, volta para on-demand" | A conclusão custa dinheiro à toa. Spot com checkpoint retoma de onde parou; spot sem checkpoint recomeça do zero, e a diferença entre os dois não é do spot, é da configuração. O script já procura checkpoint na inicialização — essa linha existe para esta falha, e sem `checkpoint_s3_uri` no estimador ela nunca encontra nada |
| Artefato sobrescrito no mesmo caminho S3 | Rodar dois Runs apontando `output_path` para o mesmo prefixo sem sufixo de job | Os dois Runs aparecem no Experiments, com métricas distintas, e tudo parece certo no painel | A proveniência está mentindo: os dois Runs apontam para o mesmo objeto, e o objeto é o do último que terminou. Reproduzir o Run mais antigo devolve o artefato do mais novo, e a comparação entre eles é uma comparação de um modelo consigo mesmo. Registro de metadado sem imutabilidade do artefato dá a sensação de rastreabilidade sem a coisa |
A primeira injeção é a que o time vai encontrar de verdade
Semente, spot e caminho de saída são erros de configuração, e configuração se corrige uma vez. Dado de origem que muda entre dois treinos é a condição NORMAL de uma plataforma viva — o Feature Store recebe ingestão o tempo todo. Rode a primeira injeção primeiro, e repare que a única defesa é o corte temporal registrado no Run.
O cientista de dados da Cadência re-executa hoje o notebook local que gerou o modelo em produção há três meses, apontando para o mesmo Feature Store. A métrica resultante é diferente da original. Por que a arquitetura MÍNIMA deste módulo não garante que as duas métricas batam?
Quando algo não funciona
| Sintoma | Causa provável | Como investigar | Onde olhar | Correção |
|---|---|---|---|---|
| Reprodução dá métrica muito diferente, além da tolerância declarada | hiperparâmetro faltando no Run original (ex.: dropout não registrado), ou seed fixada só em parte das bibliotecas | comparar o dicionário `Parameters` dos dois Runs campo a campo via API | SageMaker Experiments, Run details | completar o registro de hiperparâmetros faltantes; revisar `fixar_seed` para cobrir todas as bibliotecas usadas |
| Job Spot interrompido nunca retoma, sempre recomeça do zero | checkpoint_s3_uri não configurado no Estimator, ou o script nunca chama `buscar_checkpoint` antes de inicializar o modelo | conferir se existe objeto em s3://cadencia-ml/checkpoints/ logo após uma interrupção simulada | log do script de treino, linha 'nenhum checkpoint -- treino comeca do zero' | adicionar checkpoint_s3_uri ao Estimator e garantir que o script procura o checkpoint antes de criar o modelo |
| Run não aparece no SageMaker Experiments depois do job terminar | chamada a `run.log_parameters`/`log_metric` falhou silenciosamente por falta de permissão IAM, ou `load_run()` não achou o contexto | conferir o log do container por exceção da chamada ao SDK de Experiments | CloudWatch Logs do Training Job, grupo /aws/sagemaker/TrainingJobs | adicionar `sagemaker:AddAssociation` e `sagemaker:CreateTrialComponent` à policy do papel de execução |
| Hash do snapshot do Run não bate com o snapshot atual do Feature Store | o Feature Store foi atualizado depois deste treino — o dado de hoje já não é o mesmo do Run consultado | comparar o snapshot_hash do Run com o identificador mais recente exposto pelo Feature Store do L72 | metadado do Feature Store offline (tema do L72) | isso não é bug: é o esperado quando o dado evoluiu — decida se vale retreinar sobre o snapshot novo, documentando a divergência |
A pergunta que resolve metade destes casos
Antes de mexer em qualquer configuração, pergunte: a divergência está no COMMIT, no DADO, ou no HIPERPARÂMETRO? São três investigações diferentes — commit divergente é `git diff` entre os dois hashes; dado divergente é comparar snapshot_hash; hiperparâmetro divergente é comparar o dicionário `Parameters` dos dois Runs campo a campo.
Segurança: o artefato que vira produção sem ninguém revisar
O artefato deste laboratório não passa por aprovação humana antes de estar disponível — essa disciplina só chega no L75, com o Model Registry. Até lá, o maior risco não é acesso indevido: é escopo de permissão largo demais e artefato sem criptografia dedicada.
| Risco | Probabilidade | Impacto | Controle preventivo | Detecção | Resposta |
|---|---|---|---|---|---|
| Papel de execução com leitura do bucket do Feature Store inteiro, não só o prefixo do snapshot | média | médio | policy escopada a `offline/produtos/*`, nunca ao bucket inteiro — mesmo padrão do L65 e do L70 | CloudTrail em GetObject fora do prefixo esperado, pelo papel de execução do treino | restringir a policy e reprocessar se algo foi lido fora do escopo |
| Artefato do modelo gravado sem criptografia dedicada, usando a chave padrão do bucket | baixa | médio | chave KMS própria para este treino, com rotação habilitada, referenciada explicitamente no Estimator | AWS Config checando `SSEKMSKeyId` dos objetos em modelos/ e checkpoints/ | reescrever os objetos afetados com a chave correta; revisar quem tinha acesso à chave padrão |
| Commit hash registrado no Run não corresponde a nenhum commit real (digitado à mão, errado) | média | alto | obter o commit via `git rev-parse` no momento de disparar o job, nunca por digitação manual do cientista | validação simples: o commit_hash do Run existe no histórico do repositório | corrigir o Run com o commit real, se recuperável, ou marcar o Run como não confiável |
| Papel que chama CreateTrainingJob tem também permissão de leitura direta do S3 | baixa | médio | separar identidade de quem DISPARA o job (só `sagemaker:CreateTrainingJob`) da execution role do job em si — mesma lógica do callout da seção 8 | CloudTrail comparando o principal de GetObject com o principal de CreateTrainingJob | remover a permissão de S3 direta da identidade que dispara o job |
| Checkpoint intermediário acumulando indefinidamente sem ciclo de vida | média | baixo | política de ciclo de vida no prefixo `checkpoints/`, expirando objetos de jobs já concluídos | custo crescente de armazenamento sem crescimento correspondente de treinos ativos | aplicar a política de ciclo de vida e remover checkpoints de jobs encerrados há mais de 30 dias |
O prefixo do artefato é público até a policy provar o contrário
Um bucket de modelos sem `s3:PutObject`/`s3:GetObject` restritos ao prefixo certo, e sem bloqueio de acesso público no nível do bucket, expõe o model.tar.gz — que pode carregar informação sobre o próprio dado de treino, dependendo do algoritmo — a qualquer principal com credencial válida na conta. Confirme `aws s3api get-public-access-block` no bucket antes de considerar este laboratório pronto para produção.
Observabilidade: as perguntas que o painel de treino tem de responder
Um painel deste módulo tem uma função estreita: dizer se o próximo treino vai ser reproduzível e se o último foi. Métrica de qualidade do MODELO em si (acurácia de negócio) pertence a outro painel — o do L78.
| Pergunta | Métrica ou consulta | O que significa mudar | Limiar inicial |
|---|---|---|---|
| Quantos Runs recentes têm os quatro campos de proveniência completos? | % de Runs dos últimos 30 dias com commit_hash, snapshot_hash, hiperparâmetro e métrica não vazios | queda indica que alguém está treinando fora do fluxo com `disparar_treino.py` | abaixo de 100% — não há exceção legítima para Run incompleto |
| Quantos jobs usaram Spot vs. On-Demand no mês? | contagem por `EnableManagedSpotTraining` nos Training Jobs do período | queda no uso de Spot indica alguém contornando a configuração padrão, geralmente por medo de interrupção | abaixo de 80% de uso de Spot merece pergunta ao time |
| Quantos jobs foram interrompidos e retomaram do checkpoint com sucesso? | eventos de interrupção Spot no CloudTrail cruzados com SecondaryStatusTransitions de 'Downloading' checkpoint | retomada falha indica checkpoint mal configurado — o custo de Spot deixa de compensar | menos de 100% de retomada bem-sucedida é bug, não estatística aceitável |
| A última reprodução testada bateu dentro da tolerância? | diferença relativa registrada pelo handler .NET da última chamada de reprodução | divergência crescente ao longo do tempo sugere drift silencioso na infraestrutura de treino, não só no dado | acima de 1% relativo (o limiar declarado no exemplo deste módulo) abre investigação |
| Há Run cujo snapshot_hash já não bate com o snapshot atual do Feature Store? | comparação entre snapshot_hash do Run mais recente em produção e o identificador corrente do L72 | divergência é esperada com o tempo — o alerta é sobre SABER, não sobre impedir | qualquer Run em produção com snapshot mais antigo que 90 dias |
A métrica que engana neste pipeline
'O Run existe' não significa 'o Run é reproduzível'. Um Run pode ter os quatro campos preenchidos e ainda assim divergir na reprodução, se algum hiperparâmetro relevante do framework (dropout, batch size, ordem de embaralhamento) nunca foi incluído no dicionário registrado. Cobertura de campo não é prova de suficiência — só a reprodução de fato é.
Escala: 1 modelo, 10 modelos, e o que passa a doer sem AZ nenhuma
| Volume | O que acontece | O que passa a doer | O que fazer |
|---|---|---|---|
| 1 modelo, treino sob demanda (Cadência hoje) | poucos Runs por mês, revisão manual de cada um é viável | nada | nada |
| 10 modelos (reposição, precificação, previsão de ruptura, etc.) | dezenas de Runs por semana, cada um com seu próprio Experiment | revisão manual de cada Run não escala — falta um painel agregando cobertura de proveniência | consulta agregada por API (seção de observabilidade) em vez de abrir Run por Run |
| Retrain diário automatizado (fora do escopo deste módulo, tema do L76) | um Run novo por dia, por modelo | volume de Runs dificulta achar 'o Run que está em produção agora' sem um ponteiro explícito | o L75 (Model Registry) é quem resolve isso — apontar qual Run/versão está promovida |
| Treino em instância GPU (ml.g5) para um algoritmo CPU-bound como LightGBM | GPU parece sempre 'mais rápido' e ninguém checa se o algoritmo usa CUDA | custo por hora sobe consideravelmente sem ganho de velocidade nenhum — LightGBM não usa GPU nesta configuração, então a GPU fica ociosa e cobrando | checar se o framework/algoritmo realmente usa GPU antes de pagar por ela; LightGBM ganha com mais vCPU, não com GPU |
| Falha de AZ | SageMaker Training Job é regional; artefato em S3 sobrevive normalmente | nada específico deste módulo — o artefato e o Run continuam acessíveis | garantir versionamento do bucket de modelos, mesma proteção contra apagar errado que outros módulos da série já recomendam |
| Mais de uma pessoa disparando reprodução do mesmo Run ao mesmo tempo | dois jobs de reprodução concorrentes, cada um com seu próprio nome | desperdício de custo se ninguém perceber que já existe uma reprodução em andamento | consultar Runs em status InProgress antes de disparar uma nova reprodução do mesmo commit+snapshot |
O gargalo que só aparece com muitos modelos
Um único modelo, como o de reposição da Cadência hoje, torna revisão manual de Run viável. O ganho de ter Experiments aparece de verdade quando existem múltiplos modelos e múltiplos times — é a diferença entre perguntar 'quem lembra o hiperparâmetro deste treino' e consultar a API.
Custo: o que este laboratório acrescenta à fatura
O rastreamento em si é praticamente gratuito — o que pesa é a instância de treino, com ou sem este módulo. A diferença de custo real está em Spot vs. On-Demand, e em quanta reprodução se faz por curiosidade em vez de por necessidade.
| Cenário | Volume | O que acrescenta | Tendência | Otimização |
|---|---|---|---|---|
| Piloto | poucos treinos de teste, instância pequena | minutos de instância a mais para configurar checkpoint e Experiments | desprezível | nenhuma |
| Produção pequena | Cadência hoje: 1 modelo, treino sob demanda, poucas dezenas de Runs por mês | instância Spot custa uma fração da On-Demand equivalente; checkpoint acrescenta GB-mês de armazenamento | baixa e previsível — o gargalo de custo é a instância de treino, não o rastreamento | usar Spot com checkpoint por padrão; reservar On-Demand para janelas com prazo rígido |
| Alta escala | múltiplos modelos, retrain frequente (tema do L76) | custo de treino cresce com número de modelos × frequência de retrain, não com o rastreamento em si | passa a ser linha visível se reprodução completa virar rotina em vez de exceção | usar métrica registrada como primeira checagem; reservar reprodução real para investigação |
| Dimensão | Cobra por | Cuidado |
|---|---|---|
| Instância de treino (Spot) | hora-instância, com desconto sobre o preço On-Demand equivalente | desconto varia por tipo de instância e disponibilidade de capacidade — confira no console antes de assumir um percentual fixo |
| Instância de treino (On-Demand, se usada) | hora-instância, preço cheio | reservar para quando o prazo não tolera interrupção de Spot, não como padrão |
| Armazenamento de checkpoint e artefato no S3 | GB-mês armazenado | checkpoint de treinos antigos acumula sem ciclo de vida — a mesma lição do L70 aplicada a outro prefixo |
| SageMaker Experiments (metadado de Run) | não cobra por si só — é metadado, não computação | o custo real de 'rastrear demais' é zero; o custo de 'não rastrear' é reproduzir sem conseguir |
| KMS (chave dedicada) | por chave-mês mais por requisição de criptografia/descriptografia | centavos no volume da Cadência; relevante só em escala de milhares de objetos criptografados por dia |
O custo que este módulo evita, e que não aparece em nenhuma fatura da AWS
Três meses de um modelo em produção sem ninguém saber se ele ainda é bom sobre o dado atual é risco de negócio silencioso — decisão de reposição tomada sobre um modelo potencialmente desatualizado, sem ninguém com como verificar. O custo de instância de um treino reproduzido é centavos comparado ao custo de uma recomendação de estoque errada sustentada por meses.
Well-Architected nos seis pilares
| Pilar | Situação ao fim deste laboratório | Risco que fica | Melhoria | Prioridade |
|---|---|---|---|---|
| Excelência operacional | cada treino registrado automaticamente como Run, sem passo manual separável | hiperparâmetro relevante esquecido no dicionário registrado continua possível | checklist de hiperparâmetros obrigatórios revisado a cada mudança de algoritmo | alta |
| Segurança | papel de execução escopado por prefixo, artefato criptografado por chave dedicada | commit hash pode ser digitado errado se o disparo não usar `git rev-parse` automatizado | validar o commit_hash contra o histórico do repositório antes de aceitar o job | média |
| Confiabilidade | checkpoint periódico torna interrupção de Spot recuperável, não catastrófica | checkpoint corrompido ou parcial ainda não tem verificação de integridade automática | checksum do checkpoint antes de retomar o treino a partir dele | média |
| Eficiência de performance | treino distribuído em múltiplas instâncias reduz tempo de parede | distribuição mal calibrada pode gastar mais em coordenação do que ganha em paralelismo, sem medição | medir tempo de treino por número de instâncias antes de assumir que mais é sempre melhor | baixa |
| Otimização de custos | Spot como padrão, checkpoint evitando retrabalho em interrupção | checkpoint sem ciclo de vida acumula custo de armazenamento indefinidamente | política de ciclo de vida no prefixo de checkpoints, expirando jobs concluídos | média |
| Sustentabilidade | checkpoint evita reprocessamento completo do zero a cada interrupção | reprodução testada 'por garantia' sem necessidade real desperdiça ciclo de CPU | reservar reprodução completa para investigação real, não para checagem rotineira | baixa |
Onde IA entra nesta arquitetura, e onde não entra
Este laboratório inteiro é sobre treinar um modelo — mas o PROBLEMA que ele resolve, reprodutibilidade, não é resolvido com mais inteligência artificial. É disciplina de engenharia: registrar commit, dado e hiperparâmetro de forma que sobreviva à memória de quem treinou. Nenhum modelo aprende a fazer isso por você.
Há um lugar adjacente onde IA de fato agrega — mas é uma tarefa diferente da deste módulo: SageMaker Automatic Model Tuning usa busca bayesiana para ENCONTRAR o melhor hiperparâmetro, em vez de o cientista ajustar célula por célula. É tentador ver isso como extensão natural deste laboratório, mas ele introduz um risco que reforça, em vez de substituir, o que este módulo já construiu: cada trial do tuner também precisa virar um Run rastreável, senão o tuner encontra o melhor hiperparâmetro e ninguém sabe reproduzir aquele trial específico depois — exatamente o problema original, multiplicado pelo número de trials.
| Pergunta | Resposta honesta para este módulo |
|---|---|
| Qual problema a IA resolveria aqui? | nenhum diretamente — reprodutibilidade é rastreamento de proveniência, não uma tarefa de aprendizado |
| Por que registro estruturado basta, e não precisa de um modelo para 'lembrar' o hiperparâmetro? | porque o hiperparâmetro é uma decisão humana explícita no momento do treino — gravar o valor é suficiente; não há ambiguidade para um modelo resolver |
| Onde IA entraria de fato num tema adjacente? | SageMaker Automatic Model Tuning (busca bayesiana de hiperparâmetro) — mas resolve ENCONTRAR o melhor valor, não rastrear o que foi usado |
| Qual o risco de tratar o tuning automático como substituto deste módulo? | cada trial do tuner ainda precisa de um Run — sem isso, o tuner resolve um problema e recria outro: o melhor hiperparâmetro encontrado, e ninguém sabe reproduzi-lo depois |
| Por que não agora? | a Cadência tem um modelo e um cientista de dados — o ganho de busca automática de hiperparâmetro é real, mas secundário ao problema urgente: nenhum treino atual é reproduzível, automático ou manual |
O uso de IA que parece atraente e é prematuro aqui
Ligar Automatic Model Tuning antes de ter Experiments funcionando multiplicaria o problema deste módulo: dezenas de trials rodando, cada um um treino não rastreado, e o vencedor escolhido por métrica sem proveniência nenhuma. A ordem importa — rastreabilidade primeiro, busca automática de hiperparâmetro depois, nunca o contrário.
Anti-padrões deste laboratório
| Anti-padrão | Por que alguém faz | Por que é problema | Sintoma em produção | Forma correta | Quando é aceitável |
|---|---|---|---|---|---|
| Treinar em notebook local e subir o artefato manualmente para o S3 | é mais rápido para prototipar, sem esperar provisionar um Training Job gerenciado | nada versiona commit, dado ou hiperparâmetro daquele treino específico — é exatamente o cenário que este módulo existe para corrigir | modelo em produção que ninguém consegue reproduzir três meses depois | SageMaker Training Job + Experiments, mesmo para uma exploração rápida | protótipo de uma tarde que nunca vai a produção, descartado ao fim do dia |
| Deixar a seed aleatória no valor default de cada biblioteca | 'funciona' sem configurar nada a mais, e o resultado parece bom na primeira tentativa | reprodutibilidade quebra silenciosamente entre execuções — a métrica varia sem ninguém mudar código ou dado | duas pessoas rodando o mesmo script obtêm métricas diferentes, sem explicação óbvia | seed explícita e única, aplicada a cada biblioteca que gera aleatoriedade, versionada como hiperparâmetro | nunca — custa uma linha de código fixar, o retorno é imediato |
| Registrar hiperparâmetro só quando o resultado 'parece bom' | parece economia de esforço documentar só o que interessa, e treinos ruins parecem descartáveis | sem histórico completo, não dá para comparar experimentos ruins com os bons e aprender por que um funcionou e outro não | ninguém sabe explicar por que uma tentativa anterior foi pior — a informação nunca existiu | todo Run registrado, inclusive os descartados, porque o valor está na comparação | nunca — o custo de registrar é o mesmo, tenha o resultado sido bom ou não |
| Sobrescrever o mesmo caminho de artefato a cada treino | simplifica o código de inferência, que sempre lê o mesmo caminho fixo | perde a versão anterior — se o novo modelo piora, não há para onde voltar | rollback impossível quando um modelo novo regride em produção | artefato nomeado por run_id, versão travada; a escolha de QUAL versão servir é decisão do L75, não deste módulo | protótipo de um dia, nunca um modelo que serve tráfego real |
Limpeza: o que o destroy não leva
Este laboratório cria três coisas que sobrevivem por conta própria: os artefatos e checkpoints acumulados no S3, os Runs do Experiments (sem custo, mas com volume que cresce), e a chave KMS, que tem janela de exclusão obrigatória.
#!/usr/bin/env bash
# limpar.sh -- o que o destroy nao leva, e o que continua cobrando
set -euo pipefail
PROJETO="${PROJETO:?defina PROJETO}"
# 1. Derrube o que o Terraform administra (papel, policies, chave KMS,
# versionamento do bucket).
terraform destroy -auto-approve
# 2. A CHAVE KMS nao desaparece na hora -- fica com deletion_window_in_days
# (30 dias no treino.tf) antes de ser removida de verdade. Confirme.
aws kms describe-key --key-id "alias/${PROJETO}-treino-reposicao" \
--query 'KeyMetadata.{Estado:KeyState,DataExclusao:DeletionDate}'
# Esperado: KeyState=PendingDeletion, DataExclusao ~30 dias a frente.
# 3. ARTEFATOS E CHECKPOINTS no S3 NAO sao removidos pelo destroy.
aws s3 ls "s3://cadencia-ml/modelos/" --recursive --summarize | tail -2
aws s3 ls "s3://cadencia-ml/checkpoints/" --recursive --summarize | tail -2
# Decida: manter artefatos de Runs referenciados por algum modelo em
# producao, e remover so o resto.
aws s3 rm "s3://cadencia-ml/checkpoints/" --recursive
# 4. Runs do Experiments NAO cobram por existir, mas tambem nao somem
# sozinhos -- listar antes de decidir arquivar.
aws sagemaker list-trial-components --experiment-name cadencia-modelo-reposicao \
--query 'TrialComponentSummaries[].TrialComponentName' --output table
# 5. Prova final: nada com o nome do projeto de pe fora do esperado.
aws resourcegroupstaggingapi get-resources \
--tag-filters Key=Projeto,Values="${PROJETO}" \
--query "ResourceTagMappingList[].ResourceARN" --output table
| Recurso | Sai no destroy? | Cobra parado? | Por que fica |
|---|---|---|---|
| Papel de execução e policies IAM | sim, se em Terraform | não | IAM não cobra por si só, mas um papel órfão sobrevivendo ao módulo é risco de auditoria |
| Chave KMS dedicada | agendado, não imediato | sim, por chave-mês durante os 30 dias de janela | AWS exige janela de exclusão (7 a 30 dias) antes de remover uma chave de verdade, para evitar perda irreversível de dado criptografado |
| Artefatos de modelo no S3 | não — não pertence ao Terraform, é gravado pelo job | sim, GB-mês | decisão editorial: manter artefatos de Runs que ainda são referência histórica, remover o resto |
| Checkpoints intermediários no S3 | não | sim, GB-mês | sem ciclo de vida configurado, acumula indefinidamente — mesmo padrão de dívida que o L70 já documentou para outro prefixo |
| Runs e Trials do SageMaker Experiments | não — metadado, não recurso do Terraform | não | não cobra, mas cresce sem limite — decida uma política de arquivamento se o volume de Runs virar centenas |
Apagar a chave KMS antes da janela expirar é irreversível
Se você cancelar o `deletion_window_in_days` de propósito ou deixar a janela expirar, todo objeto criptografado por aquela chave — artefato de modelo, checkpoint — fica permanentemente ilegível, mesmo que o objeto em si continue no S3. Confirme que nenhum artefato ainda referenciado por um modelo em produção usa essa chave antes de deixar a janela seguir.
Evolução em níveis: do notebook sem rastro ao pipeline que audita a si mesmo
A terceira arquitetura não é um desenho: é a resposta a QUANDO trocar de desenho. Cada nível resolve um risco deste módulo e compra outro — até o ponto em que o próprio modelo, já servindo, é quem avisa quando algo mudou embaixo dele.
Cientista treina localmente e sobe o artefato à mão para o S3 — é onde a Cadência estava até este módulo, e é como praticamente todo mundo começa.SageMaker Training Job com Spot e checkpoint, hiperparâmetro e seed versionados no commit, snapshot do Feature Store travado por hash, cada execução registrada como um Run do Experiments.Quatro modos de inferência — tempo real, serverless, assíncrono, lote — escolhidos pelo padrão real de tráfego do modelo de reposição, não por hábito (L74).SageMaker Model Registry versiona o artefato treinado aqui e formaliza aprovação humana antes de promover uma versão a produção, com caminho de volta (L75).Retrain disparado automaticamente por dado novo, com linhagem completa do dado até o modelo publicado, sem notebook nenhum no caminho crítico (L76).O modelo de reposição, já treinado de forma reproduzível e servido pelo modo certo, passa a ter monitoramento contínuo de drift sobre os mesmos dados que o L70 valida e avaliação ligada a métrica de negócio, não só técnica (tema dos laboratórios seguintes de MLOps).A ordem não é negociável, e o motivo é concreto
Promover automaticamente (nível 4) um modelo treinado sem rastreabilidade (sem o nível 2) promoveria exatamente o problema que abriu este módulo: um artefato que ninguém sabe reproduzir, só que agora com um selo de aprovação em cima. O Registry formaliza QUAL versão está em produção — ele não cria a proveniência que o treino em si precisa ter registrado primeiro.
Resumo: problema, peça e motivo
| Problema | Peça | Por que ela, e não outra |
|---|---|---|
| Ninguém reproduz o modelo em produção | commit, hash de snapshot e hiperparâmetro registrados juntos num Run do SageMaker Experiments | consultável por API meses depois, independente de quem treinou ainda estar no time |
| Treino perde progresso quando a instância Spot é interrompida | checkpoint periódico em S3, com retomada automática | torna Spot seguro de usar — resolve resiliência e custo ao mesmo tempo |
| Dado de treino muda de leitura para leitura | snapshot do Feature Store travado por hash, não 'o mais recente' | pré-requisito para qualquer comparação de métrica entre execuções fazer sentido |
| Seed aleatória nunca fixada explicitamente | seed única, versionada como hiperparâmetro, aplicada a cada biblioteca que gera aleatoriedade | sem isso, a mesma configuração produz métrica diferente a cada execução |
| Papel de execução com escopo largo demais | policy IAM por prefixo — leitura só do snapshot, escrita só de artefato e checkpoint | mesma disciplina do L65 e do L70 aplicada a um novo domínio (treino, não pipeline de dado) |
| Métrica bit a bit é meta irreal em treino multi-thread de CPU | prova de reprodutibilidade DENTRO DE TOLERÂNCIA numérica declarada | honesto sobre a limitação real de hardware, em vez de prometer determinismo que treino distribuído não entrega |
| Qual versão está de fato em produção, e como reverter | não resolvido neste módulo | risco residual aceito e documentado; é o assunto do L75 (Model Registry e rollback) |
| Falha | O que a protege | O que ela NÃO protege |
|---|---|---|
| Hiperparâmetro perdido entre execuções | registro estruturado no Run, dentro do script de treino, antes de o job terminar | hiperparâmetro que o script nunca chegou a registrar (campo esquecido no dicionário) |
| Instância Spot interrompida no meio do treino | checkpoint periódico com retomada automática | checkpoint corrompido ou parcial, sem verificação de integridade |
| Dado mudando de leitura para leitura | snapshot travado por hash, registrado no Run | o Feature Store em si mudar de cálculo — isso é garantia do L72, não deste módulo |
| Papel de execução acessando dado fora do escopo | policy IAM por prefixo específico | identidade que DISPARA o job ter permissão de S3 direta — separação tratada no callout da seção 8 |
| Modelo pior promovido a produção sem aprovação | nada neste módulo | é o L75 inteiro, com Model Registry |
- Cientista comita o script com hiperparâmetro e seed como argumento estruturado, não valor solto.
- O papel de execução e a chave KMS autorizam o job antes de qualquer leitura de dado.
- O job lê um snapshot travado do Feature Store, identificado por hash, do L72.
- Múltiplas instâncias Spot treinam em paralelo, salvando checkpoint periódico no S3.
- Se uma instância é interrompida, o job novo retoma do último checkpoint, sem recomeçar do zero.
- Ao terminar, o script registra commit, hash de dado, hiperparâmetro completo e métrica num Run do Experiments.
- O artefato final é gravado por run_id, sem sobrescrever execuções anteriores.
- Meses depois, qualquer pessoa consulta o Run pela API e reproduz o treino dentro de uma tolerância declarada.
Perguntas frequentes
❓ Por que fixar a seed não garante reprodutibilidade bit a bit em treino multi-thread?
❓ O que o SageMaker Experiments registra automaticamente, sem eu escrever código a mais?
❓ Preciso do Feature Store do L72 para este laboratório funcionar?
❓ Por que usar instância Spot para treino, se a AWS pode interromper a qualquer momento?
❓ Um Run do Experiments substitui o Model Registry do L75?
❓ Quanto tempo leva para reproduzir o treino original a partir do commit e do hash do dado?
❓ O que acontece se eu esquecer de configurar checkpoint_s3_uri no Estimator?
Fixando
A arquitetura de PRODUÇÃO usa múltiplas instâncias Spot para treinar. O que este desenho faz quando a AWS interrompe uma das instâncias no meio do treino?
Dois Runs do SageMaker Experiments compartilham o mesmo commit, o mesmo hash de snapshot do Feature Store e o mesmo hiperparâmetro completo, incluindo seed. Treinados na mesma instância CPU multi-thread, a métrica final dos dois difere na quinta casa decimal. O que este desenho considera 'reproduzível' nesse caso?
Conhecimentos, próximo módulo e documentação
| Item | Conteúdo |
|---|---|
| Conhecimentos anteriores necessários | tema do L70 (contrato de dado sobre prata_produtos) e do L72 (Feature Store: mesmo cálculo de feature no treino e na inferência) — este módulo assume que existe um snapshot de feature versionado e identificável por hash |
| Conhecimentos adquiridos | diferença entre reprodutibilidade de PROVENIÊNCIA (commit, dado, hiperparâmetro) e determinismo BIT A BIT de treino multi-thread de CPU; checkpoint e retomada em instância Spot; o que o SageMaker Experiments registra automaticamente vs. o que exige log manual; papel de execução escopado por prefixo |
| Limitação que fica | qual versão está de fato promovida a produção, e como reverter para uma anterior — não é resolvido aqui, é o assunto do L75 (Model Registry e rollback) |
| Próximo exemplo recomendado | L74 — Servir: tempo real, serverless, assíncrono ou lote. Usa o artefato reproduzível deste módulo e resolve o próximo risco: qual modo de inferência combina com o padrão real de tráfego do modelo de reposição |
| Também habilitado por este módulo | L75 (Registry e promoção com rollback) depende de um treino rastreável — é o que este laboratório entrega — para que uma versão promovida tenha proveniência completa por trás, não só um artefato binário sem histórico |
| Data da última validação técnica | 8 de agosto de 2026 |
Documentação oficial consultada: Amazon SageMaker Training Jobs — configuração de instâncias Spot gerenciadas, `max_wait`, `max_run` e `checkpoint_s3_uri`; Amazon SageMaker Experiments — SDK Python (`sagemaker.experiments.run`), o que `load_run()` captura automaticamente e o que exige chamada explícita de `log_parameters`/`log_metric`; AWS Identity and Access Management — escopo de policy por prefixo de S3 e uso de chave KMS dedicada por recurso; documentação do LightGBM sobre reprodutibilidade multi-thread (`deterministic`, `force_row_wise`). Nenhum valor de preço de instância aparece neste módulo por decisão: confira o AWS Pricing Calculator, porque preço varia por região, tipo de instância e disponibilidade de capacidade Spot, e envelhece mais rápido que o conteúdo.
O que não foi verificado, e você deve conferir na sua conta
O nome exato dos métodos do SDK de Experiments (`load_run`, `log_parameters`, `log_metric`) e o comportamento padrão de `EnableManagedSpotTraining` podem variar entre versões do SDK do SageMaker — confira a referência atual antes de copiar o código deste módulo linha a linha. O limiar de tolerância de 1% usado no exemplo de reprodução é ilustrativo para a métrica rmse_validacao deste cenário; calibre o seu de acordo com o que a métrica do seu modelo realmente tolera.
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…