Parte V — No mundo real · Cap. V.2

Sistemas de ML

O modelo é a fração pequena. Os outros 95%.

◐ essencial🕒 estado da arte 2026-08revisão 2026-08-10📖 ~35 min de leitura🎯 12 exercícios⬇ md

Objetivos de aprendizagem

  • O1. Descrever os componentes de um sistema de ML além do modelo.
  • O2. Identificar as formas de dívida técnica específicas de sistemas que aprendem.
  • O3. Projetar o contrato entre treino e inferência para evitar training-serving skew.
  • O4. Decidir entre predição em lote e em tempo real a partir do requisito, não do gosto.

O problema: o modelo funciona, o sistema apodrece

O modelo entrou em produção e acertou. Seis meses depois, o mesmo time não consegue mais mexer nele.

Trocar um atributo de entrada exige reajustar o modelo inteiro, e ninguém sabe dizer o que vai acontecer. Um script sem dono junta três tabelas todo dia às 4h. Existe um arquivo de configuração com centenas de linhas que nunca passou por revisão. E um segundo modelo, de outro time, lê a saída do primeiro — o primeiro time descobriu isso por acaso.

Nada disso é bug. Nada disso aparece num teste. E, principalmente, nada disso tem uma linha no orçamento — porque cada item desses é caro sem ter nome. O erro que este capítulo previne é tratar o modelo como se fosse o sistema: ele é uma peça pequena de uma máquina grande, e os modos de falha caros moram fora dele.

De onde isto veio

O aperto. Entre 2014 e 2015, dentro do Google, sistemas de Machine Learning (ML) em produção acumulavam um custo de manutenção que nenhum vocabulário de engenharia existente nomeava. Todo mundo sentia; ninguém conseguia escrever numa planilha.

O que se fazia antes. Tratava-se o modelo como o sistema. A "cola" entre os componentes era invisível — não por ser pequena, mas porque não tinha nome.

A virada. Não foi inventar uma técnica. Foi importar um vocabulário pronto de outro campo, o de dívida técnica, da engenharia de software, e usá-lo para tornar o custo legível a quem decide orçamento. Um time de dez autores publicou o diagnóstico no NIPS 2015, e o vocabulário pegou.

A ideia reaproveitável. Um problema que não tem nome não entra no orçamento. Antes de inventar solução, verifique se o que falta é a palavra. Custo sem nome não é priorizado, não é medido e não é defendido em reunião — ele apenas cresce, e depois alguém leva a culpa por "lentidão do time".

O nome. Technical debt é de Ward Cunningham, num addendum aos anais da OOPSLA '92: "Shipping first time code is like going into debt." Entregar código na primeira versão é como contrair uma dívida — útil, desde que se pague antes que os juros comam o time.

A leitura que fecha (📖)

A figura 1 do artigo de 2015 é o par da AlexNet do capítulo III.4. Duas imagens que atravessaram um campo inteiro: uma é um limite de 3 GB desenhado; a outra é uma caixa preta pequena no meio de treze retângulos. Nenhuma das duas é um resultado — as duas são argumentos visuais, e é por isso que sobreviveram. Um resultado é superado pelo resultado seguinte; um argumento visual só cai quando o argumento deixa de valer.

Procedência das afirmações desta seção:

Selo Afirmação
✓ Tudo o que é atribuído a Sculley, Holt, Golovin, Davydov, Phillips, Ebner, Chaudhary, Young, Crespo e Dennison — figura 1, princípio CACE, dívida de configuração, pipeline jungles e os trechos citados entre aspas — de Hidden Technical Debt in Machine Learning Systems (NIPS, 2015), lido por inteiro
✓ O aperto (2014–15, dentro do Google) e a ausência de vocabulário para o custo de manutenção
✓ A metáfora do plumbing (encanamento) não é dos autores: no artigo ela é creditada a Lin e Ryaboy. O crédito some em quase toda repetição da metáfora — registre-o quando repetir
✓ᵐ Technical debt, de Ward Cunningham, addendum aos anais da OOPSLA '92, e a frase "Shipping first time code is like going into debt."
✓ O feature store na descrição do Michelangelo, plataforma de ML do Uber, como repositório central de atributos canônicos compartilhados entre times, em "Meet Michelangelo: Uber's Machine Learning Platform"
⏳ A data de setembro de 2017 para a aparição pública do termo, e a redação de que a camada "garante consistência entre offline e online": o texto aberto descreve o repositório compartilhado, e não confirma essa formulação. Nova tentativa em 2026-08-13 de reabrir a página para conferir a data: o domínio passou a responder 406, e a data segue não conferida
✓ As regras #31 e #32 de Rules of Machine Learning, de Martin Zinkevich (Google), lidas no texto da própria regra — com a #32 como receita central: reutilizar o código entre treino e serviço
✓ Que a formulação canônica de train/serve skew ocupe a faixa das regras #29 a #37: o documento tem uma seção intitulada "Training-Serving Skew" que começa depois da regra #28 e termina na #37, onde entra a "ML Phase III". Dessa seção vêm também a definição citada e as três causas. Página baixada e o texto varrido aqui
📖 A leitura de que a virada foi importar vocabulário, e de que a figura 1 e a AlexNet são o par de imagens-argumento do campo

Fundamentos: o que é o sistema, além do modelo

A frase que o artigo repete, e que vale decorar: "only a tiny fraction of the code in many ML systems is actually devoted to learning or prediction" — apenas uma fração minúscula do código é de fato aprendizado ou predição. A legenda da figura 1 diz o resto: "The required surrounding infrastructure is vast and complex."

O que ocupa o espaço restante, componente a componente:

Componente O que faz Onde falha
Coleta trazer o dado de onde ele nasce (ver cap. I.2) fonte muda de esquema sem avisar
Validação de dados recusar o que está fora do contrato não existe, e o lixo entra calado
Extração de atributos transformar dado bruto em entrada do modelo calculado de um jeito no treino, de outro no serviço
Infraestrutura de serviço responder à chamada latência, versão errada em produção
Monitoramento perceber que algo mudou mede a máquina, não a predição
Gerenciamento de processos orquestrar quem roda quando dependências implícitas entre jobs

Os autores chamam esse entorno, citando Lin e Ryaboy, de plumbing — encanamento. É uma boa palavra: encanamento só é notado quando vaza.

Por que dependências de dados são piores que dependências de código

Esta é a afirmação mais útil do artigo, e a que mais gente ignora: dependências de dados custam mais que dependências de código.

A razão é seca. Para dependência de código existe ferramenta — o compilador acusa, o linker quebra, o teste falha. Para dependência de dados não há compilador. Você consome um sinal produzido por outro time, aquele time muda a definição do sinal, e nada quebra: o modelo simplesmente piora, devagar, e ninguém liga uma coisa à outra. E há o efeito que dá nome ao princípio mais citado do artigo:

"No inputs are ever really independent. We refer to this here as the CACE principle: Changing Anything Changes Everything."

Nenhuma entrada é realmente independente: mudar qualquer coisa muda tudo. E o princípio não se limita aos sinais de entrada — vale para hiperparâmetros, configurações de aprendizado, métodos de amostragem, limiares de convergência e seleção de dados. Remover um atributo aparentemente inútil não devolve o modelo anterior menos aquele atributo: devolve outro modelo.

Exercíciosistemas-de-ml-e1escolha uma

Você vai apresentar a arquitetura do sistema de recomendação da empresa. Qual descrição é fiel ao que um sistema de ML em produção é?

Exercíciosistemas-de-ml-e4escolha uma

Um time descreve assim o sistema de risco de crédito dele: um job noturno lê a base de propostas, um script calcula os atributos, o modelo treinado é salvo em disco, e uma API carrega esse arquivo e responde às consultas do time comercial. Existe um painel com uso de processador, memória e tempo de resposta da API.

Pela lista de componentes deste capítulo, qual deles o relato não menciona nenhuma vez?

Exercíciosistemas-de-ml-e5escolha uma

Seis meses depois, o mesmo sistema de risco de crédito está com todos os indicadores verdes: a API responde em 40 ms, nenhuma requisição falha, a memória está estável. Ainda assim, a inadimplência entre as propostas aprovadas dobrou.

Qual descrição do sistema explica melhor essa combinação?

As dívidas que têm nome

Nomear é o serviço que o artigo presta. Cada item abaixo é um custo que você já pagou sem saber chamar.

Glue code. O código que existe só para fazer um pacote de propósito geral caber no seu problema: adaptadores, conversões de formato, empacotamentos. É a maior parte do que se escreve em volta de uma biblioteca de ML, e não faz nada de ML.

Pipeline jungle. Caso particular de glue code, no lado dos dados. Os autores a descrevem como "a jungle of scrapes, joins, and sampling steps" — uma selva de raspagens, junções e amostragens que cresce por acréscimo, um sinal novo de cada vez. E o diagnóstico é organizacional, não técnico: "symptomatic of integration issues that may have a root cause in overly separated 'research' and 'engineering' roles." A selva é sintoma de papéis de pesquisa e engenharia separados demais. Quem for consertar a selva reescrevendo scripts, e não a divisão de trabalho, vai fazer uma selva nova.

Modelos em cascata e o correction cascade. Você tem um modelo que funciona. Precisa dele para um problema ligeiramente diferente, e o caminho barato é aprender uma correção por cima da saída dele. Depois outra correção por cima da correção. Cada camada acrescenta dependência e prende o sistema a um mínimo local: melhorar o modelo de baixo agora piora os de cima, que foram treinados para corrigir os defeitos antigos dele.

Consumidores não declarados. A sua saída está num arquivo, num tópico, numa tabela. Alguém a lê — e você não sabe quem. Isso transforma qualquer mudança sua num incidente de outra pessoa, e é a dívida mais silenciosa da lista: ela só aparece no dia em que você melhora alguma coisa.

Dívida de configuração. A que quase todo time subestima. O artigo é direto: "In a mature system which is being actively developed, the number of lines of configuration can far exceed the number of lines of the traditional code." Num sistema maduro em desenvolvimento ativo, as linhas de configuração podem superar em muito as de código. E ele traz os exemplos datados, do tipo "o atributo A foi registrado incorretamente de 14/09 a 17/09" — o tipo de detalhe que vive num arquivo de configuração e decide o resultado do treino. Dos seis princípios de boa configuração que o artigo lista, o último é o que mais dói e o mais fácil de adotar: "Configurations should undergo a full code review and be checked into a repository."

Exercíciosistemas-de-ml-e6escolha uma

Metade do código do repositório do time converte o dataframe para o formato que a biblioteca espera, renomeia colunas, embrulha a chamada de treino e desembrulha o resultado. Nada disso faz aprendizado nem predição.

Que nome este capítulo dá a esse custo?

Exercíciosistemas-de-ml-e7escolha uma

Dois times descrevem problemas diferentes. No time A, um job diário raspa quatro fontes, junta, amostra, e ganha um passo novo toda vez que alguém pede um sinal novo; ninguém consegue dizer de cor o que o job faz hoje. No time B, o modelo de preço aprende sobre a saída do modelo de demanda, e uma terceira etapa corrige o preço para as lojas com estoque baixo.

Qual par de nomes descreve A e B, nessa ordem?

Reduzir a dívida: versionar, contratar, apagar

Três movimentos, em ordem de retorno por esforço.

1. Versione o dado e a configuração como você versiona código. Configuração revisada e no repositório é a recomendação literal do artigo. Dado versionado é o que permite responder à pergunta que sempre aparece depois de um incidente: com qual dado exatamente este modelo foi treinado? Sem versão, essa pergunta não tem resposta — só opinião.

2. Escreva contratos de entrada. Um contrato declara, para cada atributo, o tipo, a faixa aceitável, a taxa de nulos tolerada e quem é o dono. O que o contrato compra é o compilador que não existe: com ele, uma mudança silenciosa na fonte vira falha ruidosa na sua fronteira, e não uma degradação lenta seis semanas depois. É o mesmo assunto de qualidade e vazamento do capítulo I.3, agora escrito como código executável.

3. Apague código morto. Atributo que não é mais usado, ramo experimental que nunca saiu, modelo que ninguém consulta. Cada um deles é superfície para o CACE agir e para um consumidor não declarado aparecer. Apagar é a única forma de pagamento de dívida que não cria dívida nova.

O contrato entre treino e serviço

O train/serve skew é a fronteira mais cara do sistema. A definição da fonte é mais larga que a intuição corrente: "training-serving skew is a difference between performance during training and performance during serving", e ela lista três causas, não uma. Divergência em como o dado é tratado nos dois encanamentos; mudança no dado entre o momento de treinar e o de servir; e realimentação entre o modelo e o algoritmo.

Este capítulo trata sobretudo da primeira, que é a mais barata de evitar e a mais cara de descobrir: o mesmo atributo calculado de dois jeitos, um no treino, em lote, com a tabela inteira disponível, e outro no serviço, sob latência, com o que chegou na requisição. A segunda é o drift do capítulo V.3, e a terceira é o laço de realimentação que a regra #36 da mesma fonte trata à parte.

A formulação canônica está nas Rules of Machine Learning, de Martin Zinkevich: o documento tem uma seção com esse nome exato, "Training-Serving Skew", e ela vai da regra #29 à #37 ✓. A regra #32 é a receita inteira, e é curta: reutilize o código entre treino e serviço. Não "escreva os dois com cuidado", não "documente a fórmula": reutilize o mesmo código, de modo que a divergência se torne impossível em vez de improvável.

A feature store é a versão de plataforma dessa ideia: uma camada compartilhada que serve o mesmo atributo ao treino e à inferência. O termo aparece publicamente em setembro de 2017, na descrição do Michelangelo, plataforma de ML do Uber (⏳). O custo é que ela é mais uma peça de encanamento para manter — e o CACE também vale para ela.

Exercíciosistemas-de-ml-e8escolha uma

A regra #32 manda reutilizar o código entre treino e serviço. Um colega propõe, no lugar, documentar a fórmula do atributo num wiki que as duas equipes consultam antes de mexer.

O que a proposta dele deixa de comprar?

Exercíciosistemas-de-ml-e2escolha todas que valem

Sua equipe calcula media_de_compras_90d num job Spark noturno para treinar, e reimplementa o mesmo atributo em Java no serviço de inferência. Quais medidas atacam de fato o train/serve skew? Marque todas que valem.

Exercíciosistemas-de-ml-e9responda com suas palavras

O seu modelo de evasão usa o atributo dias_desde_ultimo_login. No treino ele sai da tabela de eventos, com o histórico inteiro à disposição; no serviço, ele vem do que a aplicação manda na requisição.

Projete o contrato dessa fronteira. Diga o que o contrato declara, onde ele é verificado, o que acontece quando é violado, e o que você muda no código para que a divergência deixe de ser possível.

Exercíciosistemas-de-ml-e3responda com suas palavras

Um sistema de previsão de demanda funciona assim: um job diário raspa três bancos internos e uma planilha do time comercial, junta tudo e amostra os últimos 18 meses. Sobre a saída do modelo de demanda, um segundo modelo aplica uma correção sazonal, e um terceiro corrige a correção para as lojas do Nordeste. O time de precificação lê a tabela final — o time de demanda descobriu isso no mês passado. O comportamento do sistema é regido por um arquivo params.yaml de 600 linhas que fica na máquina do analista sênior.

Liste as dívidas ocultas deste sistema, usando os nomes do capítulo, e diga por onde você começaria a pagar — justificando a ordem.

Decidir a forma de serviço pelo requisito

A seção anterior resolveu como o atributo é calculado. Falta quando a predição é calculada, e essa é uma decisão de desenho, não de implantação: ou ela é calculada antes, em lote, e fica guardada até alguém pedir, ou é calculada na hora em que o pedido chega.

A regra #32 abre justamente por aí, antes de falar em reúso de código:

"Batch processing is different than online processing. In online processing, you must handle each request as it arrives (e.g. you must do a separate lookup for each query), whereas in batch processing, you can combine tasks (e.g. making a join)."

Processamento em lote é diferente de processamento em linha: no segundo você atende cada requisição quando ela chega, enquanto no primeiro você pode combinar tarefas. As três formas usuais:

Forma Quando serve Latência típica
Lote a decisão pode esperar horas; as predições são calculadas de uma vez e guardadas minutos a horas
Em linha a decisão é pedida na hora, uma requisição de cada vez milissegundos
Fluxo a decisão acompanha um fluxo contínuo de eventos segundos

Quatro eixos decidem entre elas, e nenhum deles é gosto.

1. Quando a decisão é necessária. É o eixo mais fácil e o único que quase todo time considera. Se o resultado é consumido por uma tela que abre de manhã, ou por uma campanha que sai na terça, a predição pode ser calculada de madrugada. Se ela responde a um clique, não pode.

2. Quanto custa calcular o atributo, dentro do orçamento de latência. É o eixo decisivo, e o que mais gente descobre tarde. Quando a predição é feita na hora, o custo de obter os atributos entra inteiro no tempo que o usuário espera. Huyen é direta: "Because real-time features are computed upon receiving prediction requests, their computation latency adds directly to user-facing latency." Se calcular o atributo custa 800 ms e o orçamento da tela é 100 ms, o modelo em linha já perdeu, por mais preciso que seja.

A escala real ajuda a calibrar. No Michelangelo, do Uber, o serviço reporta "P95 latency of less than 5 milliseconds (ms)" sem buscar atributo externo, e "less than 10ms" quando busca atributos no Cassandra. O orçamento inteiro de uma predição em linha cabe na casa de poucos milissegundos, e é dentro dele que o cálculo do atributo precisa caber.

Há o caso extremo, em que o custo não é alto e sim proibitivo: o atributo simplesmente não pode ser calculado sob demanda. O texto do Michelangelo dá o exemplo de que "it is not possible to directly query the UberEATS order service to compute the average meal prep time for a restaurant over a specific period of time". Quando o atributo é uma agregação sobre uma janela de histórico, alguém precisa tê-la calculado antes, e isso força pré-cálculo mesmo num sistema que responde na hora.

3. Frescor. Predição guardada envelhece, e o mundo não espera pelo próximo job. É a regra #31, que descreve o mesmo mecanismo do lado do atributo: "Between training and serving time, features in the table may be changed. Your model's prediction for the same document may then differ between training and serving." A pergunta a fazer é quanto de desatualização a decisão tolera. Atributo recalculado quase em tempo real fica com defasagem "in the order of seconds", enquanto o de um job diário passa o dia inteiro envelhecendo.

4. Volume e desperdício. Em lote você calcula para a base toda, inclusive para quem nunca vai aparecer, e paga por isso; em compensação, combina o trabalho numa junção só e o custo por predição despenca. Em linha você só calcula para quem pediu, e paga por manter a capacidade de responder a qualquer momento.

A armadilha, que tem duas bocas

A primeira boca é escolher em linha por modernidade. A recomendação existe e é de autora conhecida: Huyen escreve que "Batch prediction is largely a product of legacy systems" e que "If you're building a new ML system today, it's possible to start with online prediction." Leia isso como o que é, uma recomendação de autora e não um fato do campo, porque o eixo 2 continua valendo depois dela. Quem adota a recomendação sem medir o custo do atributo chega ao mesmo lugar do exemplo do UberEATS, com um sistema em linha que precisa de uma agregação que ninguém consegue calcular a tempo.

A segunda boca é escolher lote e esquecer o relógio. A predição estava certa quando foi calculada, e é servida horas depois como se ainda estivesse. Esse defeito não aparece em nenhuma métrica de teste, porque no teste a predição e o rótulo são do mesmo instante.

Procedência das afirmações desta seção:

Selo Afirmação
✓ As regras #31 e #32 de Rules of Machine Learning, de Martin Zinkevich, e os trechos citados entre aspas, conferidos no texto da regra
✓ Os números de latência e o exemplo do tempo de preparo do UberEATS, de "Meet Michelangelo: Uber's Machine Learning Platform"
✓ As frases atribuídas a Chip Huyen sobre latência de atributo, defasagem e sistemas legados, de "Real-time machine learning: challenges and solutions", 2022
✓ᵐ Que a escolha é tratada como assunto de projeto na indústria: é o capítulo 7 de Designing Machine Learning Systems, com a seção "Batch Prediction Versus Online Prediction", e são os padrões 16 e 17 de Machine Learning Design Patterns. Sumários conferidos; o corpo dos dois capítulos está atrás de paywall e não foi lido
❌ A escolha não vem das fontes-base dos dois capítulos: procurei batch, online, latency e real-time no artigo de Sculley et al. (2015) e o contraste não está lá, nem está no Continuous Delivery for Machine Learning
📖 A ordem dos quatro eixos, e a leitura de que o custo do atributo é o eixo decisivo por ser o que o time descobre mais tarde
Exercíciosistemas-de-ml-e10escolha uma

Um banco calcula, para cada cliente, a propensão a aceitar uma oferta de crédito. A lista de clientes propensos é usada pela equipe de campanha, que monta o disparo de e-mail de segunda-feira olhando o resultado na sexta.

Qual forma de serviço o requisito pede?

Exercíciosistemas-de-ml-e11escolha uma

Uma loja quer ordenar as vitrines da página inicial no momento em que ela abre. O orçamento de latência da página para essa chamada é de 80 ms. Um dos atributos do modelo é a média de tempo de entrega do vendedor nos últimos 30 dias, e consultar o serviço de pedidos para calculá-la leva cerca de 400 ms.

Qual desenho atende ao requisito?

Exercíciosistemas-de-ml-e12escolha todas que valem

Um sistema antifraude decide, no instante da compra, se o cartão passa. Hoje ele serve em lote: de hora em hora, um job recalcula um escore de risco por cartão, e a autorização apenas consulta o escore guardado. O time relata que fraudes em rajada, concentradas em poucos minutos, passam quase todas.

Quais afirmações sustentam a mudança para serviço em linha? Marque todas que valem.

Síntese — o que levar

  • O código de aprendizado é uma fração pequena do sistema. O resto é encanamento — e encanamento só é notado quando vaza.
  • Um problema que não tem nome não entra no orçamento. Às vezes o que falta não é solução: é a palavra.
  • Dependências de dados são piores que dependências de código, porque não existe compilador que as acuse. Quem substitui o compilador é o contrato de entrada.
  • CACE: mudar qualquer coisa muda tudo — inclusive hiperparâmetro, amostragem e limiar de convergência. Não existe mudança local num sistema que aprende.
  • Glue code, pipeline jungle, cascata de correções, consumidores não declarados e configuração: cinco custos que você já paga — agora com nome. E configuração é código: revisão e repositório, sempre.
  • Contra train/serve skew, reutilize o código entre treino e serviço. Documentar a fórmula duas vezes não é contrato.
  • Apagar código morto é a única forma de pagar dívida sem criar dívida nova.
  • A forma de serviço sai do requisito, por quatro eixos: quando a decisão é necessária, quanto custa calcular o atributo dentro do orçamento de latência, quanto de defasagem a decisão tolera, e o volume. O eixo do custo do atributo é o que se descobre tarde, e o que decide.
  • A operação contínua desse sistema (o que monitorar, quando retreinar, como implantar) é o capítulo V.3, a sequência direta deste. A decisão de não lançar está no capítulo II.8.

Verificação

  1. Desenhe, para um sistema que você conhece, os componentes além do modelo. Qual deles não existe hoje — e o que essa ausência já custou?
  2. Um colega propõe remover um atributo "que não faz diferença" e diz que o resto do modelo fica igual. Por que o CACE contradiz isso, e o que você pediria antes de aprovar?
  3. Escolha um atributo do seu sistema e descreva como ele é calculado no treino e no serviço. Se as duas descrições não citarem o mesmo código, qual é o seu plano — e por que "conferir com cuidado" não é um plano?