Sistema de predicción de ganancias usando NVIDIA Triton Inference Server con ensemble de preprocesamiento Python + inferencia ONNX optimizada.
Esta arquitectura implementa un servidor de inferencia de alto rendimiento usando Triton Server, donde el preprocesamiento y la inferencia se ejecutan como un ensemble de modelos nativamente en Triton.
Cliente → Triton (HTTP/gRPC) → Ensemble:
├─ Preprocesamiento (Python)
└─ Inferencia (ONNX)
→ Respuesta
Componentes:
- Triton Server: Servidor de inferencia optimizado de NVIDIA
- Modelo Python (preproc_profit): Preprocesamiento en Python dentro de Triton
- Modelo ONNX (profit): Inferencia con ONNX Runtime optimizado
- Ensemble (profit_ensemble): Orquestación del pipeline completo
- Alto rendimiento: Throughput muy superior (~1000 req/s)
- Aceleración GPU: Soporte nativo para inferencia en GPU
- Batching dinámico: Agrupa requests automáticamente para optimizar throughput
- Múltiples backends: Soporta ONNX, TensorFlow, PyTorch, TensorRT
- Métricas integradas: Prometheus metrics out-of-the-box
- Ensemble nativo: Pipeline de modelos orquestado por Triton
- Optimización automática: Múltiples instancias, scheduling inteligente
- Menor flexibilidad: El preprocesamiento en Triton es menos flexible que en Python puro
- Curva de aprendizaje: Requiere aprender configuración de Triton (config.pbtxt)
- Debugging complejo: Más difícil debuggear que código Python estándar
- Tamaño de imagen: Imágenes Docker más grandes (~10GB)
- ✅ Alto volumen de requests (>1000 req/s)
- ✅ Inferencia con GPU para máximo rendimiento
- ✅ Pipeline de modelos estable (preprocesamiento no cambia frecuentemente)
- ✅ Necesidad de batching dinámico sofisticado
- ✅ Producción con requisitos estrictos de latencia
- ✅ Múltiples modelos que necesitan ser servidos simultáneamente
Los modelos no están incluidos en este repositorio. Debes obtenerlos y colocarlos en la estructura correcta.
model_repository/
├── preproc_profit/
│ ├── 1/
│ │ ├── model.py # ← Preprocesamiento (ya incluido)
│ │ └── preprocessing_info.pkl # ← DESCARGAR
│ └── config.pbtxt # ← Ya incluido
├── profit/
│ ├── 1/
│ │ ├── model.onnx # ← DESCARGAR
│ │ └── model.onnx.data # ← DESCARGAR (si aplica)
│ └── config.pbtxt # ← Ya incluido
└── profit_ensemble/
├── 1/ # ← Vacío
└── config.pbtxt # ← Ya incluido
# Descargar modelo ONNX
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/
# Descargar preprocessing info
gsutil cp gs://tu-bucket/models/serving/preprocessing_info.pkl \
model_repository/preproc_profit/1/
# O desde S3 (AWS)
aws s3 cp s3://tu-bucket/models/serving/model.onnx \
model_repository/serving/1/
aws s3 cp s3://tu-bucket/models/serving/preprocessing_info.pkl \
model_repository/preproc_profit/1/# 1. Entrenar y exportar modelo a ONNX
import torch.onnx
import pickle
# Exportar modelo
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
preprocessing_info = {
'encoders': {...}, # LabelEncoders
'scaler': scaler, # StandardScaler
'feature_columns': [...],
'categorical_columns': [...]
}
with open('model_repository/preproc_profit/1/preprocessing_info.pkl', 'wb') as f:
pickle.dump(preprocessing_info, f)Modelo ONNX (profit):
- Input:
input- Tensor FP32 de forma[-1, 15] - Output:
output- Tensor FP32 de forma[-1, 1] - Backend: ONNX Runtime
Preprocessing Info (preproc_profit):
- Debe contener
encoders,scaler,feature_columns,categorical_columns - Compatible con el código en
model_repository/preproc_profit/1/model.py
El sistema utiliza un pipeline de inferencia con tres componentes:
- preproc_profit: Modelo Python que preprocesa los datos raw (fechas, categorías, etc.)
- profit: Modelo ONNX que realiza la predicción basado en features procesadas
- profit_ensemble: Pipeline que orquesta el flujo de datos entre preprocesamiento y predicción
graph LR
A[Datos Raw JSON] --> B[preproc_profit]
B --> C[Features Procesadas]
C --> D[profit ONNX]
D --> E[Predicción]
profic-triton/
├── model_repository/ # Repositorio de modelos para Triton
│ ├── preproc_profit/ # Modelo de preprocesamiento Python
│ │ ├── 1/ # Versión 1
│ │ │ ├── model.py # Lógica de preprocesamiento
│ │ │ └── preprocessing_info.pkl # Encoders y scalers
│ │ └── config.pbtxt # Configuración de Triton
│ ├── profit/ # Modelo ONNX de predicción
│ │ ├── 1/ # Versión 1
│ │ │ └── model.onnx # Modelo entrenado
│ │ └── config.pbtxt # Configuración de Triton
│ └── profit_ensemble/ # Pipeline ensemble
│ ├── 1/ # Versión 1 (vacío)
│ └── config.pbtxt # Configuración del pipeline
├── test_client.py # Cliente de prueba
├── requirements.txt # Dependencias Python
├── Dockerfile # Imagen Docker personalizada
└── triton-deployment-docker.yaml # Despliegue en Kubernetes
# Crear entorno virtual
python3 -m venv venv
source venv/bin/activate # En Windows: venv\Scripts\activate
# Instalar dependencias
pip install -r requirements.txt# Opción 1: Imagen oficial de NVIDIA
docker run --rm \
-p 8000:8000 \
-p 8001:8001 \
-p 8002:8002 \
-v $(pwd)/model_repository:/models \
nvcr.io/nvidia/tritonserver:24.10-py3 \
tritonserver --model-repository=/models
# Opción 2: Construir imagen personalizada (recomendado)
docker build -t profit-triton:latest .
docker run --rm \
-p 8000:8000 \
-p 8001:8001 \
-p 8002:8002 \
profit-triton:latest# Verificar salud del servidor
curl -v http://localhost:8000/v2/health/ready
# Verificar modelos cargados
curl http://localhost:8000/v2/modelspython test_client.pyTriton Server expone una API HTTP/REST en el puerto 8000 y gRPC en el puerto 8001.
| Endpoint | Método | Descripción |
|---|---|---|
/v2/health/live |
GET | Verificar si el servidor está vivo |
/v2/health/ready |
GET | Verificar si el servidor está listo |
/v2/models |
GET | Listar todos los modelos disponibles |
/v2/models/{MODEL_NAME} |
GET | Información de un modelo específico |
/v2/models/{MODEL_NAME}/ready |
GET | Verificar si un modelo está listo |
/v2/models/{MODEL_NAME}/infer |
POST | Realizar inferencia |
/metrics |
GET | Métricas Prometheus (puerto 8002) |
Verifica si el servidor Triton está vivo.
URL: http://localhost:8000/v2/health/live
Response:
{
"live": true
}Ejemplo con curl:
curl http://localhost:8000/v2/health/liveVerifica si el servidor está listo para recibir requests de inferencia.
URL: http://localhost:8000/v2/health/ready
Response:
{
"ready": true
}Ejemplo con curl:
curl http://localhost:8000/v2/health/readyLista todos los modelos cargados en el servidor.
URL: http://localhost:8000/v2/models
Response:
[
{
"name": "preproc_profit",
"version": "1",
"state": "READY"
},
{
"name": "profit",
"version": "1",
"state": "READY"
},
{
"name": "profit_ensemble",
"version": "1",
"state": "READY"
}
]Ejemplo con curl:
curl http://localhost:8000/v2/modelsObtiene información detallada de un modelo específico.
URL: http://localhost:8000/v2/models/profit_ensemble
Response:
{
"name": "profit_ensemble",
"versions": ["1"],
"platform": "ensemble",
"inputs": [
{
"name": "raw_data",
"datatype": "BYTES",
"shape": [-1, 1]
}
],
"outputs": [
{
"name": "profit_prediction",
"datatype": "FP32",
"shape": [-1, 1]
}
]
}Ejemplo con curl:
curl http://localhost:8000/v2/models/profit_ensemble
curl http://localhost:8000/v2/models/profit
curl http://localhost:8000/v2/models/preproc_profitRealiza inferencia con el modelo especificado.
URL: http://localhost:8000/v2/models/profit_ensemble/infer
Request Body (JSON):
{
"inputs": [
{
"name": "raw_data",
"shape": [1, 1],
"datatype": "BYTES",
"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.0, \"Total_Cost\": 6920.0}"]
}
]
}Response:
{
"model_name": "profit_ensemble",
"model_version": "1",
"outputs": [
{
"name": "profit_prediction",
"datatype": "FP32",
"shape": [1, 1],
"data": [2410.0]
}
]
}Ejemplo con curl:
curl -X POST http://localhost:8000/v2/models/profit_ensemble/infer \
-H "Content-Type: application/json" \
-d '{
"inputs": [{
"name": "raw_data",
"shape": [1, 1],
"datatype": "BYTES",
"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.0, \"Total_Cost\": 6920.0}"]
}]
}'Obtiene métricas Prometheus del servidor Triton.
URL: http://localhost:8002/metrics
Response: Formato Prometheus text
# HELP nv_inference_request_success Number of successful inference requests
# TYPE nv_inference_request_success counter
nv_inference_request_success{model="profit_ensemble",version="1"} 42
# HELP nv_inference_request_duration_us Cumulative inference request duration in microseconds
# TYPE nv_inference_request_duration_us counter
nv_inference_request_duration_us{model="profit_ensemble",version="1"} 125000
...
Ejemplo con curl:
curl http://localhost:8002/metrics- Tipo: Modelo Python personalizado
- Input:
raw_features- JSON string con datos de venta - Output:
processed_features- Tensor FP32 con features procesadas - Funciones:
- Extracción de características temporales (año, mes, día, día de semana)
- Codificación de variables categóricas
- Normalización de features numéricas
- Manejo de valores faltantes
- Tipo: Modelo ONNX (LightGBM/XGBoost/NN)
- Input:
input- Features procesadas FP32 - Output:
output- Predicción de profit FP32 - Optimizado para: CPU con ONNX Runtime
- Tipo: Pipeline ensemble
- Input:
raw_data- JSON string con datos raw - Output:
profit_prediction- Valor predicho de profit - Pipeline:
- raw_data → preproc_profit → processed_features
- processed_features → profit → profit_prediction
El sistema espera datos en formato JSON con los siguientes campos:
{
"Order_Date": "2024-01-15",
"Ship_Date": "2024-01-18",
"Order_ID": "ORD-2024-001",
"Product_Name": "Office Chair",
"Category": "Furniture",
"Sub_Category": "Chairs",
"Segment": "Corporate",
"Country": "United States",
"City": "New York",
"State": "NY",
"Postal_Code": "10001",
"Region": "East",
"Product_ID": "FUR-CH-10001",
"Customer_ID": "CUST-001",
"Customer_Name": "John Doe",
"Ship_Mode": "Standard Class",
"Quantity": 2,
"Discount": 0.1,
"Sales": 299.99
}import json
import numpy as np
import tritonclient.http as httpclient
client = httpclient.InferenceServerClient("localhost:8000")
# Crear datos de prueba
data = {"Order_Date": "2024-01-15", "Sales": 299.99, ...}
json_data = json.dumps(data)
# Crear input
inputs = [httpclient.InferInput("raw_features", [1, 1], "BYTES")]
inputs[0].set_data_from_numpy(np.array([[json_data]], dtype=object))
# Inferencia
results = client.infer("preproc_profit", inputs=inputs)
processed = results.as_numpy("processed_features")# Usar el ensemble directamente
inputs = [httpclient.InferInput("raw_data", [1, 1], "BYTES")]
inputs[0].set_data_from_numpy(np.array([[json_data]], dtype=object))
results = client.infer("profit_ensemble", inputs=inputs)
prediction = results.as_numpy("profit_prediction")
print(f"Profit predicho: ${prediction[0][0]:.2f}")# 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:8001Edita model_repository/preproc_profit/1/model.py para ajustar:
- Manejo de nuevas características
- Lógica de transformación
- Validación de datos
- Entrena tu nuevo modelo
- Exporta a ONNX
- Reemplaza
model_repository/serving/1/model.onnx - Actualiza
preprocessing_info.pklsi cambian los 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
model_repository/preproc_profit/1/ - Asegúrate de que contiene: encoders, scaler, feature_columns, categorical_columns
- Revisa logs:
docker logs <container_id> - Verifica configuración: Nombres de input/output deben coincidir entre modelos
- El número de features procesadas debe coincidir con lo que espera el modelo ONNX
- Usa
onnxlibrary para inspeccionar el modelo:
import onnx
model = onnx.load("model_repository/serving/1/model.onnx")
print(model.graph.input[0])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- El preprocesador maneja automáticamente valores faltantes
- Las variables categóricas desconocidas se manejan con valores por defecto
- El pipeline es stateless - cada request es independiente
- Para producción, considera usar GPU para el modelo ONNX si el volumen lo justifica