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:

  1. 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.
  2. 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.
  3. 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_size deve ser int
  • Validação de faixa: lr deve ser > 0
  • Validação cruzada entre campos: precision=bf16 exige 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.txt e git_diff.patch (ou um marcador “dirty: true/false”)
  • Um run_manifest.json com 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:

  1. Buscar dados
  2. Validar dados
  3. Pré-processamento/engenharia de atributos (feature engineering)
  4. Treinar
  5. Avaliar
  6. Empacotar
  7. 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)

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”:

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”.