Treinamento reprodutível (configs, artefatos)
Por que o treinamento reproduzível importa
“Treinamento reproduzível (reproducible training)” significa que você consegue recriar o resultado de um modelo a partir das mesmas entradas — ou, pelo menos, explicar com precisão por que não consegue. Em sistemas modernos de aprendizado de máquina (machine learning, ML), uma execução de treinamento (training run) é o produto de muitas partes móveis:
- Configuração (configuration): hiperparâmetros (hyperparameters), fontes de dados, definições de atributos (feature definitions), aumento de dados (augmentation), configurações de avaliação (evaluation settings)
- Código (code): loop de treinamento (training loop), pré-processamento (preprocessing), arquitetura do modelo (model architecture), funções de perda (loss functions)
- Dados (data): conjuntos de dados brutos (raw datasets), partições (splits), rótulos (labels), regras de filtragem (filtering rules), estratégia de amostragem (sampling strategy)
- Ambiente (environment): versões de bibliotecas (library versions), CUDA/cuDNN, flags de compilador (compiler flags), sistema operacional, hardware
- Aleatoriedade (randomness): inicialização, embaralhamento, dropout (dropout), kernels de GPU não determinísticos (nondeterministic GPU kernels)
Sem uma abordagem disciplinada para gerenciamento de configuração (config management), armazenamento de artefatos (artifact storage) e pipelines reproduzíveis (reproducible pipelines), você pode acabar com:
- “Melhorias fantasma” que você não consegue executar novamente
- Incapacidade de depurar regressões
- Modelos em produção que não podem ser rastreados de volta às entradas (um risco de governança)
- Iteração lenta porque cada execução é um floco de neve artesanal
Este artigo foca em práticas práticas, de ponta a ponta, que tornam as execuções de treinamento explicáveis e repetíveis, e complementa tópicos como Rastreamento de Experimentos (Experiment Tracking) e Versionamento (Dados, Código, Modelos) (Versioning (Data, Code, Models)).
O que “reproduzível” realmente significa (três níveis)
Reprodutibilidade não é binária; é útil definir um alvo:
Reprodutibilidade bit a bit (bitwise reproducibility) (estrita)
Executar o mesmo código + entradas resulta em pesos e métricas idênticos, bit a bit.- Muitas vezes é difícil com GPUs, treinamento distribuído (distributed training), precisão mista (mixed precision) e kernels não determinísticos.
Reprodutibilidade estatística (statistical reproducibility) (padrão prático)
Reexecutar resulta em métricas dentro de uma tolerância esperada (por exemplo, ±0,2% de acurácia).- Tipicamente aceitável para iteração de pesquisa e muitos retreinamentos em produção.
Reprodutibilidade funcional (functional reproducibility) (nível de sistema)
Você consegue recriar o mesmo artefato liberado reexecutando um pipeline fixado, ou pode recuperá-lo de um repositório de artefatos (artifact store) com linhagem completa.- Muitas vezes, é o mais importante para produção: você sempre consegue obter o modelo exato que foi implantado.
Em operações de aprendizado de máquina (MLOps) em produção, a garantia mais forte normalmente vem da linhagem de artefatos (artifact lineage) (“este é o modelo que enviamos, aqui estão suas entradas”), e não de esperar que o treinamento em GPU seja determinístico bit a bit.
Princípio central: toda execução deve ter uma “impressão digital (fingerprint)”
Uma execução de treinamento deve ter uma impressão digital auditável contendo:
- Configuração resolvida (resolved config) (após padrões + sobrescritas (overrides))
- Versão do código (SHA de commit do git, estado sujo)
- Versão dos dados (ID do snapshot do conjunto de dados, hash da consulta ou commit do DVC)
- Versão do ambiente (digest da imagem Docker e/ou lockfile)
- Política de semente aleatória (random seed policy)
- Artefatos produzidos (checkpoints (checkpoints), métricas (metrics), gráficos (plots), relatórios de avaliação (eval reports))
Uma regra simples: se pode mudar o resultado, precisa ser registrado.
Gerenciamento de configuração: de flags ad-hoc a configurações tipadas e validadas
O que torna configurações difíceis em ML
Configurações em ML não são apenas “configurações de aplicativo”. Elas codificam:
- Hiperparâmetros (LR, tamanho do lote, agenda de warmup)
- Lógica de processamento de dados (filtros, mapeamento de rótulos, tokenização (tokenization))
- Topologia de treinamento (paralelismo de dados distribuído (Distributed Data Parallel, DDP), acumulação de gradiente (gradient accumulation), precisão)
- Definições de avaliação (conjuntos de dados, métricas, fatiamento, limiares)
- Destinos de logging/monitoramento
Se as configurações ficam espalhadas por scripts de shell, notebooks e argumentos padrão, a reprodutibilidade se degrada rapidamente.
Boas práticas para configuração
1) Use uma única configuração estruturada como fonte de verdade
Prefira uma configuração hierárquica (YAML/TOML/JSON) ou um objeto de configuração tipado que possa ser serializado.
Exemplo de config.yaml:
project: "fraud-detector"
run:
seed: 1234
output_dir: "runs/${now:%Y-%m-%d}/${run_id}"
data:
train_uri: "s3://ml-data/fraud/v7/train.parquet"
valid_uri: "s3://ml-data/fraud/v7/valid.parquet"
label: "is_fraud"
features:
- amount
- merchant_id
- country
model:
name: "mlp"
hidden_sizes: [256, 128]
dropout: 0.2
train:
epochs: 10
batch_size: 1024
lr: 0.001
weight_decay: 0.0001
precision: "bf16"
gradient_accumulation: 1
eval:
metrics: ["auc", "logloss"]
threshold: 0.5
logging:
system: "mlflow"
tags:
owner: "risk-team"
purpose: "baseline"
2) Suporte sobrescritas, mas sempre registre a configuração resolvida
Ferramentas como Hydra/OmegaConf, gin-config, ou até mesmo uma camada argparse disciplinada podem funcionar — o que importa é:
- Padrões são centralizados
- Sobrescritas são explícitas (CLI ou ambiente)
- A configuração resolvida final é salva como um artefato
Exemplo com uma sobrescrita via CLI:
python train.py train.lr=3e-4 train.batch_size=2048 data.train_uri=s3://ml-data/fraud/v8/train.parquet
Mesmo se você não usar Hydra, pode imitar o padrão: analisar sobrescritas, mesclá-las e escrever a saída mesclada.
3) Valide configurações (tipos + restrições)
Muitas falhas de treinamento e execuções irreproduzíveis vêm de erros silenciosos de configuração.
- Validação de tipo:
batch_sizedeve ser int - Validação de faixa:
lrdeve ser > 0 - Validação cruzada entre campos:
precision=bf16exige hardware compatível
Em Python, equipes comumente usam Pydantic ou dataclasses + checagens manuais.
4) Separe “parâmetros” de “segredos”
Não armazene tokens/credenciais em artefatos de configuração. Use variáveis de ambiente ou um gerenciador de segredos (secret manager), e garanta que logs/artefatos não capturem segredos. Veja Privacidade em Logs.
5) Gere um hash estável de configuração
Um hash estável facilita:
- Deduplicar execuções
- Criar IDs de execução determinísticos
- Vincular artefatos a configurações
Exemplo:
import json, hashlib
def config_hash(cfg: dict) -> str:
payload = json.dumps(cfg, sort_keys=True, separators=(",", ":")).encode("utf-8")
return hashlib.sha256(payload).hexdigest()[:12]
Armazene o hash tanto nos metadados da execução quanto nos caminhos de artefatos.
O que armazenar para reprodutibilidade de configuração
No mínimo, cada execução deve armazenar:
config.resolved.yaml(após interpolação/sobrescritas)argv.txt(a linha de comando exata)git_commit.txtegit_diff.patch(ou um marcador “dirty: true/false”)- Um
run_manifest.jsoncom ponteiros para todos os artefatos
Este é o “rastro de papel” no qual você vai confiar meses depois.
Armazenamento de artefatos: trate saídas como objetos imutáveis e endereçáveis
O que conta como um artefato?
Um sistema de treinamento reproduzível armazena entradas, intermediários e saídas:
Entradas
- Snapshots de conjunto de dados ou resultados de consulta
- Dicionários de rótulos, mapeamentos de classes
- Tokenizadores (tokenizers) / arquivos de vocabulário (vocab)
- Referência ao modelo base (base model) (para ajuste fino (fine-tuning)): nome do modelo + revisão exata (exact revision)
Intermediários
- Conjuntos de dados pré-processados
- Estatísticas de atributos (médias/variâncias de normalização)
- Embeddings ou índices em cache (se usados)
- Checkpoints durante o treinamento
Saídas
- Pesos finais do modelo
- Grafo/exportação para inferência (inference) (TorchScript, ONNX)
- Métricas (JSON), matrizes de confusão, gráficos de calibração (calibration plots)
- Relatórios de avaliação, métricas por fatia (slice metrics)
- Cards/resumos para governança (por exemplo, “cartão de modelo (model card)”)
Para ajuste fino de modelos de linguagem grandes (large language model, LLM), armazene também templates de prompt e regras de formatação (frequentemente gerenciados junto a Operações de Prompts (PromptOps)).
Objetivos do armazenamento de artefatos
Um bom repositório de artefatos fornece:
- Imutabilidade (immutability): artefatos não devem mudar “no lugar”
- Endereçabilidade (addressability): cada artefato tem um ID/caminho único
- Linhagem (lineage): conectar artefatos à execução/configuração/dados/código
- Acessibilidade (accessibility): recuperação fácil em CI, treinamento, implantação
- Políticas de retenção (retention policies): manter o que importa, excluir o que não importa (com cuidado)
Backends de armazenamento (storage backends) comuns incluem S3/GCS/Azure Blob, NFS (menos ideal) e plataformas de rastreamento de experimentos que gerenciam uploads de artefatos.
Organizando artefatos: um layout prático
Uma convenção simples e eficaz:
runs/
2026-01-06/
fraud-detector__c8a91d2f3b10/
manifest.json
config.resolved.yaml
argv.txt
git_commit.txt
env.lock.txt
data_manifest.json
checkpoints/
epoch_001.pt
epoch_010.pt
model/
model.pt
tokenizer.json
metrics/
train_metrics.jsonl
eval_metrics.json
plots/
roc_curve.png
Isso pode ficar localmente durante a execução e, depois, ser enviado como um pacote de artefatos.
Manifestos de dados: a peça faltante em muitos sistemas
Um modo de falha frequente: você registra hiperparâmetros e código, mas não os dados exatos.
Um manifesto de dados (data manifest) deve registrar, para cada conjunto de dados usado:
- URI de origem (ou consulta)
- ID de snapshot/versão
- Contagem de linhas
- Hash/assinatura (quando viável)
- Lógica de partição (semente, estratificação, janela de tempo)
Exemplo de trecho de data_manifest.json:
{
"train": {
"uri": "s3://ml-data/fraud/v7/train.parquet",
"etag": "9b2cf535f27731c974343645a3985328",
"rows": 18273419
},
"valid": {
"uri": "s3://ml-data/fraud/v7/valid.parquet",
"etag": "1d9a9a8d6b1c4a2e9f3d0bd0d5a5d2c1",
"rows": 2039183
},
"split_policy": {
"type": "predefined-files",
"notes": "no reshuffling"
}
}
Se você usa uma consulta (por exemplo, BigQuery/Snowflake), armazene:
- O texto da consulta
- Um hash da consulta
- O timestamp de execução
- O snapshot/ID de transação da tabela (se suportado)
Ferramentas e padrões para gerenciamento de artefatos
Você não precisa de uma ferramenta específica, mas precisa das capacidades:
- Plataformas de experimento (logging de artefatos integrado): MLflow, Weights & Biases etc.
Útil quando combinado com Rastreamento de Experimentos. - Sistemas de versionamento de dados/modelos: DVC, lakeFS, Delta Lake time travel etc.
- Armazenamentos de objetos (object stores) (S3/GCS) com nomenclatura e metadados disciplinados.
Prática-chave: nunca sobrescreva model.pt para o mesmo ID de execução. Se você retreinar, é um novo ID de execução.
Pipelines reproduzíveis: etapas determinísticas, saídas em cache e linhagem clara
Um “pipeline (pipeline)” é a sequência (frequentemente um grafo acíclico direcionado (directed acyclic graph, DAG)) que produz um modelo:
- Buscar dados
- Validar dados
- Pré-processamento/engenharia de atributos (feature engineering)
- Treinar
- Avaliar
- Empacotar
- Registrar/promover
Para ser reproduzível, um pipeline deve ser:
- Declarativo (declarative): etapas e dependências são explícitas
- Idempotente (idempotent): reexecutar uma etapa com as mesmas entradas produz as mesmas saídas
- Cacheável (cacheable): evitar recomputação quando as entradas não mudaram
- Hermético (hermetic): execução da etapa usa dependências fixadas e ambientes controlados
- Auditável (auditable): cada etapa registra entradas/saídas e versões
Isso se conecta diretamente a CI/CD para Modelos (CI/CD for Models): pipelines reproduzíveis são o que torna possível uma automação confiável.
Exemplo de pipeline: treinamento “dirigido por manifesto”
Um padrão simples, mas poderoso, é gerar um manifesto de execução no início e então passá-lo por cada etapa.
manifest.json (conceitualmente):
{
"run_id": "fraud-detector__c8a91d2f3b10",
"config_hash": "c8a91d2f3b10",
"git_commit": "a1b2c3d4",
"docker_image": "ghcr.io/acme/fraud-train@sha256:...",
"data_manifest": "artifacts://.../data_manifest.json",
"outputs": {
"model": "artifacts://.../model/model.pt",
"metrics": "artifacts://.../metrics/eval_metrics.json"
}
}
Cada etapa lê o manifesto, adiciona suas saídas e grava um manifesto atualizado. Isso torna a linhagem explícita e torna a promoção para um Registro de Modelos (Model Registry) direta.
Validação de dados como um portão de reprodutibilidade
Reprodutibilidade não é apenas “mesmo código”. Se seus dados mudam silenciosamente, você pode “reproduzir” uma execução, mas treinar algo significativamente diferente.
Adicione uma etapa de validação antes do treinamento:
- checagens de esquema, limiares de dados ausentes
- checagens de faixas e cardinalidade dos atributos
- checagens de distribuição de rótulos
- checagens de frescor/cobertura dos dados
Isso está intimamente relacionado a Validação de Dados.
Cache e limites de recomputação
Uma decisão comum de design: o que você coloca em cache?
- Coloque em cache saídas de pré-processamento quando o pré-processamento é caro e estável
- Coloque em cache estatísticas de atributos usadas em tempo de inferência (devem ser versionadas!)
- Coloque em cache snapshots de conjunto de dados se a fonte upstream é mutável
Caches de pipeline devem ser chaveados por:
- IDs de artefatos de entrada + hash da configuração + versão do código
Se você não consegue computar um hash de conteúdo (por exemplo, conjuntos de dados enormes), use IDs de snapshot mais contagens de linhas e metadados.
Determinismo no treinamento: o que você pode (e não pode) controlar
Mesmo com logging perfeito de configuração e artefatos, o treinamento pode variar devido a não determinismo.
Definição de seed (linha de base mínima)
Para stacks de ML em Python, faça seed de:
- Python
random - NumPy
- RNG do framework (PyTorch / JAX / TensorFlow)
- workers do DataLoader (se houver)
Em PyTorch, uma linha de base comum:
import os, random
import numpy as np
import torch
def set_seed(seed: int):
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
torch.cuda.manual_seed_all(seed)
# Determinism knobs (may reduce performance)
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False
torch.use_deterministic_algorithms(True)
os.environ["CUBLAS_WORKSPACE_CONFIG"] = ":4096:8"
Ressalvas importantes:
- Algumas operações não têm implementação determinística; forçar determinismo pode gerar erro.
- Treinamento distribuído pode introduzir efeitos de ponto flutuante dependentes de ordem.
- Precisão mista e kernels fundidos (fused kernels) podem mudar a numérica entre tipos de GPU.
Registre a topologia de treinamento e detalhes de runtime
Se você se importa com reprodutibilidade rigorosa, registre:
- número de GPUs, modelo da GPU
- world size, rank, configurações de DDP
- tamanho do lote por dispositivo e tamanho do lote global
- passos de acumulação de gradiente
- modo de precisão mista (fp16/bf16)
- versões do framework e do CUDA
Para muitas equipes, o objetivo prático é: reexecutar no mesmo stack gera resultados similares; produção usa artefatos armazenados para rollback exato.
Juntando tudo: uma receita prática de “execução reproduzível”
1) No início da execução: crie um diretório de execução imutável
- Gere
run_id(inclua hash da configuração + timestamp) - Grave configuração resolvida e metadados imediatamente
2) Congele código e ambiente
- Registre o SHA do git (e se a árvore de trabalho está suja)
- Use um ambiente fixado:
- Imagem Docker com um digest (
@sha256:...), ou - lockfiles (
uv.lock,poetry.lock,requirements.txt+ hashes)
- Imagem Docker com um digest (
3) Resolva e faça snapshot das entradas de dados
- Crie um manifesto de dados (URIs + IDs/hashes de snapshot)
- Opcionalmente, materialize um snapshot para fontes mutáveis
4) Treine e salve artefatos de forma determinística (tanto quanto viável)
- Salve checkpoints periódicos com números de época/passo
- Salve o artefato do modelo final separadamente
- Salve métricas e gráficos de avaliação
5) Envie artefatos para armazenamento durável
- Armazenamento de objetos ou repositório de artefatos do rastreador de experimentos
- Garanta que os artefatos sejam imutáveis e endereçados por conteúdo (content-addressed) quando possível
6) Registre/publique o modelo com linhagem completa
- Envie o modelo + manifesto para um Registro de Modelos
- Anexe configuração, métricas, manifesto de dados e referência de código
Modos de falha comuns (e como evitá-los)
“Tenho a configuração, mas não consigo reproduzir as métricas”
Causas prováveis:
- Dados mudaram (nenhum snapshot/versão registrado)
- Versões diferentes de biblioteca/CUDA
- Número diferente de GPUs ou escalonamento do tamanho do lote
- Kernels não determinísticos ou configurações diferentes de precisão
Prevenção:
- Registre manifestos de dados e detalhes do ambiente
- Prefira artefatos armazenados para modelos liberados
- Defina faixas de tolerância aceitáveis para “reprodutibilidade estatística”
“Nós sobrescrevemos o checkpoint”
Causa:
- Caminhos de artefato não vinculados a IDs de execução, ou “latest” é usado em todo lugar
Prevenção:
- Use caminhos baseados em ID de execução e nunca sobrescreva
- Use um estágio de registro (staging/production) em vez de sobrescrever blobs
“O código de pré-processamento mudou, mas o caminho do conjunto de dados não”
Causa:
- Mesma localização de saída reutilizada para lógica diferente de pré-processamento
Prevenção:
- Versione saídas de pré-processamento como artefatos
- Chaveie caminhos de cache por versão do código + hash da configuração
“Não conseguimos dizer qual modelo está implantado”
Causa:
- Sem vínculo da implantação de volta aos IDs de artefatos
Prevenção:
- Promova modelos via registro, não copiando arquivos manualmente
- Garanta que produção referencie IDs de artefatos imutáveis (digests/versões)
- Conecte a linhagem do treinamento ao Monitoramento e à avaliação online
Checklist mínimo
Uma execução de treinamento é reproduzível quando você consegue responder:
- Configuração: temos a configuração resolvida usada na execução?
- Código: temos o commit exato (e o diff, se estava sujo)?
- Dados: temos identificadores de snapshot do conjunto de dados e a lógica de partição?
- Ambiente: temos versões de dependências e de runtime (idealmente um digest de container)?
- Aleatoriedade: temos configurações de seed e a topologia de treinamento?
- Artefatos: conseguimos recuperar o modelo exato, o tokenizador e as métricas?
- Linhagem: há um manifesto conectando tudo acima?
Como isso se encaixa no stack mais amplo de MLOps
Treinamento reproduzível é a ponte entre “experimentos” e “sistemas”:
- Rastreamento de Experimentos fornece organização e comparação de execuções.
- Versionamento (Dados, Código, Modelos) fornece consistência entre ativos.
- CI/CD para Modelos operacionaliza a reprodutibilidade em gates automatizados e releases.
- Registro de Modelos transforma artefatos em releases gerenciados com promoção/rollback.
- Validação de Dados garante que as entradas do pipeline permaneçam confiáveis ao longo do tempo.
Se você implementar apenas uma ideia deste artigo, faça ser esta: toda execução de treinamento deve produzir um manifesto autocontido que consiga reconstituir as entradas da execução e recuperar suas saídas. Essa única disciplina elimina uma grande fração de incidentes de “não conseguimos reproduzir”.