Por Que Essa Arquitetura é Importante
Construir um sistema de recomendação que funciona em escala não é só escolher o modelo certo — é sobre projetar um pipeline multistage que equilibre latência, precisão e complexidade operacional. Este post detalha um sistema de nível de produção implantado no Amazon EKS, cobrindo tudo desde preparação de dados até auto-scaling.
Desafios Principais Resolvidos
- Cold-start: Usuários anônimos e novos itens recebem recomendações significativas através de feature masking durante o treinamento.
- Latência: Cache em memória das features dos itens reduziu o tempo de lookup em 99,7%.
- Atualização: Pipelines de fine-tuning diário atualizam modelos sem retreinar tudo.
- Escala: Auto-scaling do Triton Inference Server no Kubernetes lida com cargas de requisições variáveis.
A arquitetura completa é open-source e está disponível no GitHub. Para contexto sobre tendências de front-end, veja este artigo sobre CSS em 2026: Alpha Function, Grid Lanes e o que aconteceu no CSS Day.
Visão Geral do Sistema
O sistema segue o padrão clássico retrieve-rank-rerank:
- Retrieval: Modelo Two-Tower + índice FAISS ANN para geração rápida de candidatos.
- Filtragem: Bloom filter remove itens já vistos.
- Ranking: Modelo DLRM pontua candidatos usando features de usuário, item e contexto.
- Reranking: Amostragem baseada em pontuação para diversidade final.

Componentes Chave e Padrões de Código
1. Tratamento de Cold-start com Feature Masking
Para tornar o modelo robusto a usuários desconhecidos, 5% das linhas de treino têm features de usuário e contexto substituídas por valores sentinela:
# Mascarar alguns usuários e features de contexto nos dados de treino com 5% de probabilidade
USUARIO_ANONIMO = -1
OOV_GENERO = -1
OOV_CATEGORIA_TOP = -1
OOV_DISPOSITIVO = -1
diretorio_mascarado = os.path.join(caminho_entrada, "treino_mascarado")
os.makedirs(diretorio_mascarado, exist_ok=True)
for i in range(dias_treino):
dia = cudf.read_parquet(os.path.join(caminho_entrada, f"treino_dia_{i:02d}.parquet"))
n = len(dia)
mascara_usuario = cupy.random.random(n) < 0.05
dia.loc[mascara_usuario, "user_id"] = USUARIO_ANONIMO
dia.loc[mascara_usuario, "gender"] = OOV_GENERO
dia.loc[mascara_usuario, "top_category"] = OOV_CATEGORIA_TOP
mascara_dispositivo = cupy.random.random(n) < 0.05
dia.loc[mascara_dispositivo, "device_type"] = OOV_DISPOSITIVO
dia.to_parquet(os.path.join(diretorio_mascarado, f"treino_dia_{i:02d}.parquet"), index=False)
del dia
gc.collect()
Isso força o modelo a depender de features de contexto (tipo de dispositivo, hora) e embeddings OOV aprendidos — assim, até novos usuários recebem resultados personalizados.
2. Cache em Memória para Resolver Gargalo de Latência
A análise de perfil revelou que feast_item_lookup consumia 195 ms por requisição (52% da latência total). A solução foi substituir chamadas de rede por um cache NumPy local:
# Na inicialização, carregar todas as features dos itens uma vez na memória
class ConsultaItemFeast:
def __init__(self, cliente_feast, ids_itens):
self.cache = {}
for id_item in ids_itens:
features = cliente_feast.get_online_features(...)
self.cache[id_item] = np.array(features)
def consultar(self, ids_itens):
# Lookup O(1) em memória em vez de chamada de rede
return np.array([self.cache[i] for i in ids_itens])
Resultado: Redução de 99,7% na latência de lookup de features, melhoria de 54% na latência ponta a ponta e aumento de 310% na taxa de transferência com concorrência=4.
3. Ensemble do Triton Inference Server
O grafo de serviço é um DAG de 14 modelos orquestrados pelo Triton:
# Script de inicialização do Triton
set -e
DIRETORIO_MODELOS=${1:-"/model/triton_model_repository"}
echo "Iniciando Triton Inference Server"
echo "Diretório de modelos: $DIRETORIO_MODELOS"
tritonserver \
--model-repository="$DIRETORIO_MODELOS" \
--model-control-mode=explicit \
--load-model=nvt_user_transform \
--load-model=nvt_item_transform \
--load-model=nvt_context_transform \
--load-model=multimodal_embedding_lookup \
--load-model=query_tower \
--load-model=faiss_retrieval \
--load-model=dlrm_ranking \
--load-model=item_id_decoder \
--load-model=feast_user_lookup \
--load-model=feast_item_lookup \
--load-model=filter_seen_items \
--load-model=softmax_sampling \
--load-model=context_preprocessor \
--load-model=unroll_features \
--load-model=ensemble_model
Esse carregamento explícito garante comportamento previsível de cold-start e gerenciamento limpo de versões.

Limitações e Considerações Críticas
1. Contexto Apenas no Estágio de Ranking
Atualmente, o contexto da requisição (dispositivo, hora) é usado apenas pelo ranker, não pelo retriever. Isso significa que candidatos irrelevantes para um dado contexto podem ser filtrados antes do ranking. Correção: Adicionar features de contexto à query tower — já implementado em um branch separado.
2. Versionamento de Dados e Reprodutibilidade
O pipeline de treino não tem snapshots explícitos de dados. Embora um commit git fixe o código, o mesmo commit com novos dados de interação pode produzir resultados diferentes. Recomendado: Usar ferramentas como DVC ou LakeFS para versionamento de dados.
3. Sem Monitoramento de Qualidade Online
Latência e taxa de transferência são monitoradas, mas a qualidade da recomendação (ex.: CTR, engajamento) não é monitorada em produção. Deriva de performance pode passar despercebida. Próximo passo: Integrar um componente de monitoramento que dispare alertas quando métricas online se desviarem das linhas de base.
4. Lacuna de Modelagem de Sessão
A feature top_category atual é uma aproximação grosseira do interesse de curto prazo. Um encoder transformer baseado em sessão capturaria padrões comportamentais mais ricos.
Trabalhos Futuros
- Retrieval sensível a contexto: Adicionar features de dispositivo/hora à query tower para melhor qualidade de candidatos.
- Rastreamento de experimentos: Integrar MLflow ou Weights & Biases para comparar variantes de modelo.
- Registro de modelos: Formalizar linhagem entre execuções de treino, dados e modelos implantados.
- Avaliação online: Adicionar framework de teste A/B para monitorar qualidade das recomendações.

Conclusão
Esta arquitetura demonstra uma abordagem pronta para produção na construção de sistemas de recomendação que são:
- Rápidos: Cache em memória e design multistage mantêm a latência controlada.
- Atualizados: Fine-tuning diário se adapta a novos comportamentos sem retreinar tudo.
- Escaláveis: Kubernetes HPA e Karpenter escalam nós GPU sob demanda.
- Robustos: Tratamento de cold-start e Bloom filter previnem modos de falha comuns.
O código completo está disponível no repositório GitHub. Para mais sobre como plataformas de IA em larga escala lidam com otimização, veja esta análise da Plataforma Unificada de Agentes de IA da Meta.
Próximos Passos para Aprendizado
- Aprofunde-se em modelos Two-Tower com a documentação do NVIDIA Merlin.
- Explore Kubeflow Pipelines para automação de MLOps.
- Experimente modelos baseados em sessão para modelagem de interesse de curto prazo.
Este artigo é baseado em um projeto open-source de nível de produção. Todo o código e configurações são compartilhados para fins educacionais.