Sistema de predicción de ganancias con arquitectura de microservicios que combina Ray Serve como API Gateway con Triton Server como motor de inferencia independiente.
Esta arquitectura implementa una separación completa entre la capa de API/preprocesamiento (Ray Serve) y la capa de inferencia (Triton Server), ejecutándose como servicios independientes que se comunican por red.
Cliente → Ray Serve (Puerto 8080) → Preprocesamiento → Triton Server (Interno) → ONNX → Respuesta
[Público] [No expuesto]
Componentes:
- Ray Serve: API Gateway, preprocesamiento, validación
- Triton Server: Motor de inferencia ONNX (solo accesible internamente)
- Docker Network: Comunicación segura entre servicios
- Triton Client: Librería para comunicación Ray ↔ Triton
- Máxima seguridad: Triton Server no expuesto públicamente
- Escalado independiente: Ray y Triton escalan por separado
- Flexibilidad de Ray: Preprocesamiento fácil de modificar en Python
- Performance de Triton: Inferencia optimizada con ONNX Runtime
- Aislamiento: Fallos en un servicio no afectan directamente al otro
- API Gateway robusto: Control total sobre validación, autenticación, rate limiting
- Equipos separados: Un equipo maneja API, otro maneja modelos
- Mayor complejidad: Dos servicios que coordinar y mantener
- Latencia de red: Overhead de comunicación entre Ray y Triton (~5-10ms)
- Más recursos: Requiere más memoria y CPU (dos contenedores)
- Debugging complejo: Errores pueden ocurrir en múltiples lugares
- Configuración adicional: Docker Compose, networking, variables de entorno
- ✅ Producción empresarial con requisitos de seguridad
- ✅ Necesidad de escalar API y modelos independientemente
- ✅ Equipos separados para desarrollo de API y ML
- ✅ Casos donde el preprocesamiento cambia más frecuentemente que el modelo
- ✅ Arquitectura de microservicios existente
- ✅ Requisitos de aislamiento y fault tolerance
Los modelos no están incluidos en este repositorio. Debes obtenerlos y colocarlos en las ubicaciones correctas.
ray-triton-separados/
├── preprocessing_info.pkl # ← DESCARGAR (para Ray)
└── model_repository/
└── profit/
├── 1/
│ ├── model.onnx # ← DESCARGAR (para Triton)
│ └── model.onnx.data # ← DESCARGAR (si aplica)
└── config.pbtxt # ← Ya incluido
# Descargar preprocessing info para Ray
gsutil cp gs://tu-bucket/models/serving/preprocessing_info.pkl ./
# Descargar modelo ONNX para Triton
gsutil cp gs://tu-bucket/models/serving/model.onnx \
model_repository/serving/1/
gsutil cp gs://tu-bucket/models/serving/model.onnx.data \
model_repository/serving/1/
# O desde S3 (AWS)
aws s3 cp s3://tu-bucket/models/serving/preprocessing_info.pkl ./
aws s3 cp s3://tu-bucket/models/serving/model.onnx \
model_repository/serving/1/
aws s3 cp s3://tu-bucket/models/serving/model.onnx.data \
model_repository/serving/1/import torch.onnx
import pickle
# 1. Exportar modelo ONNX para Triton
torch.onnx.export(
model,
dummy_input,
"model_repository/serving/1/model.onnx",
input_names=['input'],
output_names=['output'],
dynamic_axes={'input': {0: 'batch_size'}, 'output': {0: 'batch_size'}}
)
# 2. Guardar preprocessing_info.pkl para Ray
preprocessing_info = {
'encoders': {...}, # LabelEncoders
'scaler': scaler, # StandardScaler
'feature_columns': [...], # Lista de 15 features
'categorical_columns': [...] # Lista de columnas categóricas
}
with open('preprocessing_info.pkl', 'wb') as f:
pickle.dump(preprocessing_info, f)Para Ray Serve (preprocessing_info.pkl):
- Debe contener encoders, scaler, feature_columns, categorical_columns
- Compatible con Scikit-learn
- Produce exactamente 15 features normalizadas
Para Triton Server (modelo ONNX):
- Input:
input- Tensor FP32 de forma[-1, 15] - Output:
output- Tensor FP32 de forma[-1, 1] - Versión: Opset 11 o superior
El sistema utiliza una arquitectura de microservicios con separación clara de responsabilidades:
-
Ray Serve:
- API Gateway con FastAPI
- Preprocesamiento de datos (feature engineering)
- Validación de entrada
- Comunicación con Triton
-
Triton Server:
- Servidor de inferencia optimizado
- Ejecución del modelo ONNX
- Alto rendimiento y baja latencia
graph LR
A[Cliente] -->|Puerto 8080| B[Ray Serve<br/>FastAPI]
B --> C[Preprocesamiento<br/>Local en Ray]
C --> D[15 Features<br/>Procesadas]
D -->|Triton Client| E[Triton Server<br/>🔒 No expuesto]
E --> F[Modelo profit<br/>ONNX]
F --> G[Predicción]
G --> B
B --> A
- Triton Server: Solo accesible internamente a través de la red Docker
- Ray Serve: Único punto de entrada público (puerto 8080)
- Preprocesamiento: Ejecutado localmente en Ray para mayor control y flexibilidad
- Beneficios:
- Triton Server protegido de acceso directo externo
- Ray Serve actúa como API Gateway con validación y control de acceso
- Preprocesamiento centralizado y versionado con
preprocessing_info.pkl - Separación clara entre lógica de negocio (Ray) y modelo ML (Triton)
-
Ray Serve (Preprocesamiento + API):
- Carga encoders y scalers desde
preprocessing_info.pkl - Extracción de características temporales
- Codificación de variables categóricas
- Normalización de features
- Envío de 15 features procesadas a Triton
- Carga encoders y scalers desde
-
Modelo profit (ONNX en Triton):
- Recibe array de 15 features preprocesadas
- Ejecuta inferencia con modelo ONNX optimizado
- Retorna predicción de profit
ray-triton-separados/
├── serve.py # Servicio Ray Serve con FastAPI y preprocesamiento
├── preprocessing_info.pkl # Encoders, scalers y metadata para preprocesamiento
├── requirements.txt # Dependencias Python para Ray
├── config-serve-docker.yaml # Configuración para Docker
├── serve_config.yaml # Configuración de Ray Serve
├── Dockerfile.ray # Imagen Docker para Ray Serve
├── Dockerfile.triton # Imagen Docker para Triton Server
├── docker-compose.yaml # Orquestación de servicios
├── model_repository/ # Repositorio de modelos para Triton
│ └── profit/ # Modelo ONNX de predicción
│ ├── 1/ # Versión 1
│ │ ├── model.onnx # Modelo entrenado
│ │ └── model.onnx.data # Datos del modelo
│ └── config.pbtxt # Configuración de Triton
├── test_integration.py # Tests de integración
└── README.md # Esta documentación
# Crear entorno virtual
python3 -m venv venv
source venv/bin/activate # En Windows: venv\Scripts\activate
# Instalar dependencias
pip install -r requirements.txt# URL del servidor Triton (default: localhost:8000)
export TRITON_SERVER_URL="localhost:8000"
# Nombre del modelo en Triton (default: profit)
export TRITON_MODEL_NAME="profit"
# Versión del modelo (default: 1)
export TRITON_MODEL_VERSION="1"# Construir y ejecutar ambos servicios
docker-compose up --build
# O en modo detached
docker-compose up -d# 1. Construir y ejecutar Triton Server
docker build -f Dockerfile.triton -t triton-profit-server .
docker run --rm \
-p 8000:8000 \
-p 8001:8001 \
-p 8002:8002 \
triton-profit-server
# 2. En otra terminal, ejecutar Ray Serve
serve run serve_config.yaml# Verificar Triton Server
curl http://localhost:8000/v2/health/ready
# Verificar Ray Serve
curl http://localhost:8080/health
# Ver información del modelo
curl http://localhost:8080/model-info
# Ver información del preprocesamiento
curl http://localhost:8080/preprocessing-info- Ubicación: Ejecutado localmente en Ray Serve
- Configuración:
preprocessing_info.pkl - Componentes:
- Encoders: LabelEncoders para variables categóricas
- Scaler: StandardScaler para normalización
- Feature columns: Lista de 15 características esperadas
- Categorical columns: Columnas que requieren encoding
- Funciones:
- Extracción de características temporales (año, mes, día, día de semana)
- Cálculo de días hasta envío
- Codificación de variables categóricas
- Normalización de features numéricas
- Manejo de valores faltantes
- Tipo: Modelo ONNX (LightGBM/XGBoost/NN)
- Input:
input- Array de 15 features procesadas (FP32) - Output:
output- Predicción de profit (FP32) - Dimensiones:
- Input:
[batch_size, 15] - Output:
[batch_size, 1]
- Input:
- Optimizado para: CPU con ONNX Runtime
- Cliente → Envía datos raw en JSON
- Ray Serve → Valida y preprocesa datos
- Preprocesamiento → Genera 15 features normalizadas
- Triton Server → Ejecuta inferencia con modelo ONNX
- Ray Serve → Formatea y retorna respuesta
Ray Serve expone los siguientes endpoints en el puerto 8080:
| Endpoint | Método | Descripción |
|---|---|---|
/predict |
POST | Predicción de ganancias (principal) |
/health |
GET | Verificar estado del servicio y conexión con Triton |
/model-info |
GET | Información detallada del modelo y preprocesamiento |
/preprocessing-info |
GET | Detalles de encoders, scalers y features |
/fake-email |
GET | Generador de emails falsos para pruebas |
Endpoint principal para realizar predicciones de ganancias.
URL: http://localhost:8080/predict
Request Body:
{
"Region": "Sub-Saharan Africa",
"Country": "South Africa",
"Item_Type": "Fruits",
"Sales_Channel": "Online",
"Order_Priority": "M",
"Order_Date": "2024-01-15",
"Ship_Date": "2024-01-20",
"Units_Sold": 1000,
"Unit_Price": 9.33,
"Unit_Cost": 6.92,
"Total_Revenue": 9330.00,
"Total_Cost": 6920.00
}Response (Success):
{
"status": "success",
"prediction": {
"total_profit": 2410.00,
"currency": "USD",
"formatted": "$2,410.00"
},
"input_summary": {
"region": "Sub-Saharan Africa",
"country": "South Africa",
"item_type": "Fruits",
"units_sold": 1000,
"unit_price": 9.33,
"total_revenue": 9330.00
},
"model_info": {
"name": "profit",
"version": "1",
"backend": "ONNX Runtime (Triton)",
"preprocessing": "Ray Serve (Local)"
}
}Response (Error):
{
"detail": "Error description"
}Verifica el estado del servicio y la conexión con Triton Server.
URL: http://localhost:8080/health
Response (Healthy):
{
"status": "healthy",
"triton_server": {
"is_live": true,
"model_ready": true,
"model_name": "profit",
"model_version": "1"
},
"preprocessing": {
"ready": true,
"location": "Ray Serve",
"features_count": 15,
"categorical_features": 5
}
}Response (Unhealthy):
{
"status": "unhealthy",
"error": "Error description"
}Ejemplo con curl:
curl http://localhost:8080/healthObtiene información detallada del modelo ONNX en Triton y del preprocesamiento en Ray.
URL: http://localhost:8080/model-info
Response:
{
"model": {
"name": "profit",
"versions": ["1"],
"platform": "onnxruntime_onnx",
"inputs": [
{
"name": "input",
"datatype": "FP32",
"shape": [-1, 15]
}
],
"outputs": [
{
"name": "output",
"datatype": "FP32",
"shape": [-1, 1]
}
]
},
"preprocessing": {
"features": ["Region", "Country", "Item_Type", "..."],
"categorical_columns": ["Region", "Country", "Item_Type", "Sales_Channel", "Order_Priority"],
"encoders_available": ["Region", "Country", "Item_Type", "Sales_Channel", "Order_Priority"],
"total_features": 15,
"scaler_type": "StandardScaler"
}
}Ejemplo con curl:
curl http://localhost:8080/model-infoObtiene información muy detallada del preprocesamiento, incluyendo las clases de los encoders.
URL: http://localhost:8080/preprocessing-info
Response:
{
"feature_columns": [
"Region", "Country", "Item_Type", "Sales_Channel",
"Order_Priority", "Units_Sold", "Unit_Price", "Unit_Cost",
"Total_Revenue", "Total_Cost", "Order_Year", "Order_Month",
"Order_Day", "Order_DayOfWeek", "Days_to_Ship"
],
"categorical_columns": ["Region", "Country", "Item_Type", "Sales_Channel", "Order_Priority"],
"numerical_columns": ["Units_Sold", "Unit_Price", "Unit_Cost", "..."],
"encoders": {
"Region": {
"type": "LabelEncoder",
"classes": ["Asia", "Australia and Oceania", "Central America and the Caribbean", "..."]
},
"Country": {
"type": "LabelEncoder",
"classes": ["Afghanistan", "Albania", "Algeria", "..."]
}
},
"scaler": {
"type": "StandardScaler",
"mean": [2.3, 45.6, 1.2, "..."],
"scale": [0.8, 12.3, 0.5, "..."]
}
}Ejemplo con curl:
curl http://localhost:8080/preprocessing-infoGenera un email falso para pruebas.
URL: http://localhost:8080/fake-email
Response:
{
"email": "john.doe@example.com"
}Ejemplo con curl:
curl http://localhost:8080/fake-emailEl endpoint /predict acepta datos en formato JSON con los siguientes campos:
{
"Region": "Sub-Saharan Africa",
"Country": "South Africa",
"Item_Type": "Fruits", // También acepta "Item Type"
"Sales_Channel": "Online", // También acepta "Sales Channel"
"Order_Priority": "M", // También acepta "Order Priority"
"Order_Date": "2024-01-15",
"Ship_Date": "2024-01-20",
"Units_Sold": 1000,
"Unit_Price": 9.33,
"Unit_Cost": 6.92,
"Total_Revenue": 9330.00,
"Total_Cost": 6920.00
}Nota: El API acepta nombres de campos tanto con guiones bajos (Item_Type) como con espacios (Item Type).
# Hacer una predicción
curl -X POST http://localhost:8080/predict \
-H "Content-Type: application/json" \
-d '{
"Region": "Sub-Saharan Africa",
"Country": "South Africa",
"Item_Type": "Fruits",
"Sales_Channel": "Online",
"Order_Priority": "M",
"Order_Date": "2024-01-15",
"Ship_Date": "2024-01-20",
"Units_Sold": 1000,
"Unit_Price": 9.33,
"Unit_Cost": 6.92,
"Total_Revenue": 9330.00,
"Total_Cost": 6920.00
}'
# Health check
curl http://localhost:8080/health
# Información del modelo
curl http://localhost:8080/model-infoimport requests
import json
# URL del servicio Ray Serve
url = "http://localhost:8080/predict"
# Datos de ejemplo
data = {
"Region": "Sub-Saharan Africa",
"Country": "South Africa",
"Item_Type": "Fruits",
"Sales_Channel": "Online",
"Order_Priority": "M",
"Order_Date": "2024-01-15",
"Ship_Date": "2024-01-20",
"Units_Sold": 1000,
"Unit_Price": 9.33,
"Unit_Cost": 6.92,
"Total_Revenue": 9330.00,
"Total_Cost": 6920.00
}
# Hacer predicción
response = requests.post(url, json=data)
result = response.json()
print(json.dumps(result, indent=2)){
"status": "success",
"prediction": {
"total_profit": 2410.00,
"currency": "USD",
"formatted": "$2,410.00"
},
"input_summary": {
"region": "Sub-Saharan Africa",
"country": "South Africa",
"item_type": "Fruits",
"units_sold": 1000,
"unit_price": 9.33,
"total_revenue": 9330.00
},
"model_info": {
"name": "profit",
"version": "1",
"backend": "ONNX Runtime (Triton)",
"preprocessing": "Ray Serve (Local)"
}
}Si necesitas probar directamente el servidor Triton sin pasar por Ray Serve (requiere features preprocesadas):
import numpy as np
import tritonclient.http as httpclient
# Cliente de Triton
client = httpclient.InferenceServerClient("localhost:8000")
# NOTA: Este test requiere features ya preprocesadas (15 valores)
# En producción, el preprocesamiento se hace en Ray Serve
features_procesadas = np.array([[
0.5, -0.3, 0.8, 1.2, -0.5, # Primeras 5 features (ejemplo)
0.1, 0.9, -0.2, 0.4, 0.7, # Siguientes 5 features
2024, 1, 15, 1, 5 # Últimas 5 features (temporales)
]], dtype=np.float32)
# Crear input
inputs = [httpclient.InferInput("input", [1, 15], "FP32")]
inputs[0].set_data_from_numpy(features_procesadas)
# Inferencia
outputs = [httpclient.InferRequestedOutput("output")]
results = client.infer("profit", inputs=inputs, outputs=outputs)
prediction = results.as_numpy("output")
print(f"Profit predicho: ${prediction[0][0]:.2f}")
# IMPORTANTE: Para obtener features preprocesadas correctamente,
# usa el endpoint /preprocessing-info de Ray Serve para ver
# los encoders y scalers aplicados# Desplegar Triton Server
kubectl apply -f triton-deployment-docker.yaml
# Verificar pods
kubectl get pods -l app=triton-inference-server
# Ver logs
kubectl logs -f deployment/triton-inference-serverkubectl port-forward service/triton-inference-server 8000:8000 8001:8001El preprocesamiento ahora se ejecuta en Ray Serve. Para modificarlo:
-
Actualizar
preprocessing_info.pkl:import pickle # Cargar y modificar with open('preprocessing_info.pkl', 'rb') as f: info = pickle.load(f) # info debe contener: # - 'encoders': dict con LabelEncoders # - 'scaler': StandardScaler o similar # - 'feature_columns': lista de 15 columnas # - 'categorical_columns': columnas categóricas # Guardar cambios with open('preprocessing_info.pkl', 'wb') as f: pickle.dump(info, f)
-
Modificar lógica en
serve.py:- Método
preprocess_input()para cambiar transformaciones - Método
load_preprocessing_info()para configuración inicial
- Método
- Entrena tu nuevo modelo
- Exporta a ONNX (debe aceptar 15 features)
- Reemplaza
model_repository/serving/1/model.onnx - Actualiza
preprocessing_info.pklcon nuevos encoders/scalers
Edita model_repository/serving/config.pbtxt:
dynamic_batching {
preferred_batch_size: [ 1, 4, 8, 16, 32 ]
max_queue_delay_microseconds: 100
}- Verifica que el archivo existe en la raíz del proyecto:
preprocessing_info.pkl - Asegúrate de que contiene las siguientes claves:
encoders: diccionario con LabelEncodersscaler: objeto StandardScalerfeature_columns: lista de 15 columnascategorical_columns: lista de columnas categóricas
- Revisa logs de Triton:
docker logs <container_id> - Verifica que el modelo profit está cargado:
curl http://localhost:8000/v2/models/profit
- El modelo ONNX espera exactamente 15 features
- Verifica el preprocesamiento con el endpoint:
curl http://localhost:8080/preprocessing-info
- Inspecciona el modelo ONNX:
import onnx model = onnx.load("model_repository/serving/1/model.onnx") print(model.graph.input[0]) # Debe mostrar shape [-1, 15]
- Ray Serve maneja valores desconocidos usando el valor por defecto (0)
- Para agregar nuevas categorías, actualiza
preprocessing_info.pkl
Triton expone métricas en el puerto 8002:
# Ver métricas
curl http://localhost:8002/metrics
# Métricas importantes:
# - nv_inference_request_success
# - nv_inference_request_failure
# - nv_inference_count
# - nv_inference_exec_count
# - nv_inference_queue_duration_us- Preprocesamiento en Ray: Toda la lógica de preprocesamiento se ejecuta en Ray Serve, no en Triton
- Archivo pickle: El archivo
preprocessing_info.pkles crítico - contiene encoders y scalers entrenados - 15 Features: El modelo ONNX espera exactamente 15 features preprocesadas y normalizadas
- Valores faltantes: Se manejan automáticamente con valores por defecto
- Categorías desconocidas: Se asigna valor 0 (más común) cuando encuentra categorías no vistas durante entrenamiento
- Pipeline stateless: Cada request es independiente
- Optimización: Para producción con alto volumen, considera:
- GPU para el modelo ONNX en Triton
- Múltiples réplicas de Ray Serve
- Cache de preprocesamiento para datos frecuentes