¿Por Qué Esta Arquitectura es Clave?
Construir un sistema de recomendación que funcione a escala no es solo elegir el modelo correcto — se trata de diseñar un pipeline multistage que equilibre latencia, precisión y complejidad operativa. Este post desglosa un sistema de nivel producción desplegado en Amazon EKS, cubriendo desde preparación de datos hasta auto-escalado.
Desafíos Principales Resueltos
- Cold-start: Usuarios anónimos y artículos nuevos reciben recomendaciones significativas mediante feature masking durante el entrenamiento.
- Latencia: Caché en memoria de las features de los artículos redujo el tiempo de búsqueda en 99.7%.
- Actualización: Pipelines de fine-tuning diario actualizan modelos sin reentrenar todo.
- Escala: Auto-escalado del Triton Inference Server en Kubernetes maneja cargas de solicitudes variables.
La arquitectura completa es open-source y está disponible en GitHub. Para contexto sobre tendencias de front-end, checa este artículo sobre CSS en 2026: Alpha Function, Grid Lanes y lo que pasó en CSS Day.
Vista General del Sistema
El sistema sigue el patrón clásico retrieve-rank-rerank:
- Retrieval: Modelo Two-Tower + índice FAISS ANN para generación rápida de candidatos.
- Filtrado: Bloom filter elimina artículos ya vistos.
- Ranking: Modelo DLRM puntúa candidatos usando features de usuario, artículo y contexto.
- Reordenamiento: Muestreo basado en puntuación para diversidad final.

Componentes Clave y Patrones de Código
1. Manejo de Cold-start con Feature Masking
Para hacer el modelo robusto a usuarios desconocidos, el 5% de las filas de entrenamiento tienen features de usuario y contexto reemplazadas con valores centinela:
# Enmascarar algunos usuarios y features de contexto en datos de entrenamiento con 5% de probabilidad
USUARIO_ANONIMO = -1
OOV_GENERO = -1
OOV_CATEGORIA_TOP = -1
OOV_DISPOSITIVO = -1
directorio_enmascarado = os.path.join(ruta_entrada, "entrenamiento_enmascarado")
os.makedirs(directorio_enmascarado, exist_ok=True)
for i in range(dias_entrenamiento):
dia = cudf.read_parquet(os.path.join(ruta_entrada, f"entrenamiento_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(directorio_enmascarado, f"entrenamiento_dia_{i:02d}.parquet"), index=False)
del dia
gc.collect()
Esto fuerza al modelo a depender de features de contexto (tipo de dispositivo, hora) y embeddings OOV aprendidos — así, hasta usuarios nuevos reciben resultados personalizados.
2. Caché en Memoria para Resolver Cuello de Botella de Latencia
El perfilado reveló que feast_item_lookup consumía 195 ms por solicitud (52% de la latencia total). La solución fue reemplazar llamadas de red con un caché NumPy local:
# En la inicialización, cargar todas las features de los artículos una vez en memoria
class ConsultaArticuloFeast:
def __init__(self, cliente_feast, ids_articulos):
self.cache = {}
for id_articulo in ids_articulos:
features = cliente_feast.get_online_features(...)
self.cache[id_articulo] = np.array(features)
def consultar(self, ids_articulos):
# Búsqueda O(1) en memoria en lugar de llamada de red
return np.array([self.cache[i] for i in ids_articulos])
Resultado: Reducción del 99.7% en latencia de búsqueda de features, mejora del 54% en latencia de extremo a extremo y aumento del 310% en rendimiento con concurrencia=4.
3. Ensemble del Triton Inference Server
El grafo de servicio es un DAG de 14 modelos orquestados por Triton:
# Script de inicio de Triton
set -e
DIRECTORIO_MODELOS=${1:-"/model/triton_model_repository"}
echo "Iniciando Triton Inference Server"
echo "Directorio de modelos: $DIRECTORIO_MODELOS"
tritonserver \
--model-repository="$DIRECTORIO_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
Esta carga explícita garantiza un comportamiento predecible de cold-start y una gestión limpia de versiones.

Limitaciones y Consideraciones Críticas
1. Contexto Solo en la Etapa de Ranking
Actualmente, el contexto de la solicitud (dispositivo, hora) solo lo usa el ranker, no el retriever. Esto significa que candidatos irrelevantes para un contexto dado pueden filtrarse antes del ranking. Solución: Agregar features de contexto a la query tower — ya implementado en una rama separada.
2. Versionado de Datos y Reproducibilidad
El pipeline de entrenamiento no tiene snapshots explícitos de datos. Aunque un commit de Git fija el código, el mismo commit con nuevos datos de interacción puede producir resultados diferentes. Recomendado: Usar herramientas como DVC o LakeFS para versionado de datos.
3. Sin Monitoreo de Calidad en Línea
Se monitorean latencia y rendimiento, pero la calidad de la recomendación (ej.: CTR, engagement) no se monitorea en producción. La deriva de rendimiento puede pasar desapercibida. Siguiente paso: Integrar un componente de monitoreo que active alertas cuando las métricas en línea se desvíen de las líneas base.
4. Brecha en Modelado de Sesión
La feature top_category actual es una aproximación burda del interés a corto plazo. Un encoder transformer basado en sesión capturaría patrones de comportamiento más ricos.
Trabajo Futuro
- Retrieval consciente del contexto: Agregar features de dispositivo/hora a la query tower para mejor calidad de candidatos.
- Seguimiento de experimentos: Integrar MLflow o Weights & Biases para comparar variantes de modelo.
- Registro de modelos: Formalizar linaje entre ejecuciones de entrenamiento, datos y modelos desplegados.
- Evaluación en línea: Agregar framework de pruebas A/B para monitorear calidad de recomendaciones.

Conclusión
Esta arquitectura demuestra un enfoque listo para producción en la construcción de sistemas de recomendación que son:
- Rápidos: Caché en memoria y diseño multistage mantienen la latencia controlada.
- Actualizados: Fine-tuning diario se adapta a nuevos comportamientos sin reentrenar todo.
- Escalables: Kubernetes HPA y Karpenter escalan nodos GPU bajo demanda.
- Robustos: Manejo de cold-start y Bloom filter previenen modos de fallo comunes.
El código completo está disponible en el repositorio de GitHub. Para más sobre cómo las plataformas de IA a gran escala manejan la optimización, checa este análisis de la Plataforma Unificada de Agentes de IA de Meta.
Próximos Pasos para Aprender
- Profundiza en modelos Two-Tower con la documentación de NVIDIA Merlin.
- Explora Kubeflow Pipelines para automatización de MLOps.
- Experimenta con modelos basados en sesión para modelado de interés a corto plazo.
Este artículo está basado en un proyecto open-source de nivel producción. Todo el código y configuraciones se comparten con fines educativos.