# Servidor de inferencia Triton

**NVIDIA Triton Inference Server** es una plataforma de servicio de inferencia de código abierto y nivel de producción que admite prácticamente todos los principales marcos de ML. Diseñada para servir con alto rendimiento y baja latencia, Triton maneja PyTorch, TensorFlow, ONNX, TensorRT, OpenVINO y más — todo desde un único proceso de servidor. Desplégalo en la nube con GPU de Clore.ai para obtener una infraestructura de inferencia escalable y rentable.

***

## ¿Qué es Triton Inference Server?

Triton es la respuesta de NVIDIA al desafío de servir modelos de ML a escala:

* **Multi-framework:** PyTorch, TensorFlow, TensorRT, ONNX, OpenVINO, backends personalizados en Python
* **Ejecución concurrente:** Múltiples modelos, múltiples instancias por GPU
* **Batching dinámico:** Agrupa automáticamente las solicitudes para un mayor rendimiento
* **gRPC + HTTP:** Protocolos estándar de la industria listos para usar
* **Métricas:** Punto de métricas compatible con Prometheus
* **Repositorio de modelos:** Gestión de modelos basada en el sistema de archivos

**Puertos usados:**

| Puerto | Protocolo | Propósito              |
| ------ | --------- | ---------------------- |
| 8000   | HTTP      | API de inferencia REST |
| 8001   | gRPC      | API de inferencia gRPC |
| 8002   | HTTP      | Métricas de Prometheus |

***

## Prerrequisitos

| Requisito      | Mínimo                        | Recomendado     |
| -------------- | ----------------------------- | --------------- |
| VRAM GPU       | 8 GB                          | 16–24 GB        |
| GPU            | Cualquier NVIDIA con CUDA 11+ | RTX 4090 / A100 |
| RAM            | 16 GB                         | 32 GB           |
| Almacenamiento | 20 GB                         | 50 GB           |

{% hint style="info" %}
Triton también admite inferencia solo en CPU para cargas de trabajo que no usan CUDA. Usa la `variant` variante de la imagen Docker para ahorrar costos en trabajos por lotes que no requieren GPU.
{% endhint %}

***

## Paso 1 — Alquila una GPU en Clore.ai

1. Inicia sesión en [clore.ai](https://clore.ai).
2. Haz clic **Marketplace** y filtra por VRAM ≥ 16 GB.
3. Selecciona un servidor y haz clic en **Configurar**.
4. Establecer imagen Docker: **`nvcr.io/nvidia/tritonserver:24.01-py3`**
   * (Reemplaza `24.01` con la versión más reciente — consulta [catálogo NGC](https://catalog.ngc.nvidia.com/orgs/nvidia/containers/tritonserver))
5. Establecer puertos abiertos: `22` (SSH), `8000` (HTTP), `8001` (gRPC), `8002` (métricas).
6. Haz clic **Alquilar**.

{% hint style="warning" %}
Las imágenes Docker de Triton son grandes (\~15–20 GB). Permite 3–5 minutos para la primera descarga en el primer inicio. Los inicios posteriores son rápidos.
{% endhint %}

***

## Paso 2 — Dockerfile personalizado (con SSH)

La imagen oficial de Triton no incluye un servidor SSH. Usa este Dockerfile:

```dockerfile
FROM nvcr.io/nvidia/tritonserver:24.01-py3

RUN apt-get update && apt-get install -y \
    openssh-server \
    wget curl \
    && rm -rf /var/lib/apt/lists/*

# Configurar SSH
RUN mkdir /var/run/sshd && \
    echo 'root:clore123' | chpasswd && \
    sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config

# Instalar la biblioteca cliente de Python
RUN pip install tritonclient[all] numpy Pillow

RUN mkdir -p /models

EXPOSE 22 8000 8001 8002

CMD service ssh start && \
    tritonserver \
        --model-repository=/models \
        --log-verbose=0 \
        --http-port=8000 \
        --grpc-port=8001 \
        --metrics-port=8002
```

***

## Paso 3 — Entender el Repositorio de Modelos

Triton carga modelos desde un **repositorio de modelos** — un directorio con una estructura específica:

```
/models/
├── model_name_1/
│   ├── config.pbtxt          # Configuración del modelo
│   ├── 1/                    # Versión 1
│   │   └── model.pt          # Archivo del modelo
│   └── 2/                    # Versión 2 (opcional)
│       └── model.pt
├── model_name_2/
│   ├── config.pbtxt
│   └── 1/
│       └── model.onnx
```

Cada modelo necesita:

1. Un directorio con el nombre del modelo
2. Un `config.pbtxt` archivo de configuración
3. Al menos un subdirectorio de versión (p. ej., `1/`) con el archivo del modelo

***

## Paso 4 — Desplegar un Modelo PyTorch

### Exportar el modelo a TorchScript

```python
import torch
import torchvision

# Cargar un ResNet50 preentrenado
model = torchvision.models.resnet50(pretrained=True)
model.eval()

# Exportar a TorchScript
example_input = torch.randn(1, 3, 224, 224)
traced_model = torch.jit.trace(model, example_input)

# Guardar
traced_model.save("/tmp/resnet50.pt")
print("Modelo exportado con éxito")
```

### Configurar el Repositorio de Modelos

```bash
# SSH en tu instancia de Clore.ai
ssh root@<clore-host> -p <port>

# Crear la estructura de directorios
mkdir -p /models/resnet50/1

# Subir el modelo
# (desde tu máquina local)
scp -P <port> /tmp/resnet50.pt root@<clore-host>:/models/resnet50/1/model.pt
```

### Crear config.pbtxt

```bash
cat > /models/resnet50/config.pbtxt << 'EOF'
name: "resnet50"
platform: "pytorch_libtorch"
max_batch_size: 32

input [
  {
    name: "input__0"
    data_type: TYPE_FP32
    dims: [3, 224, 224]
  }
]

output [
  {
    name: "output__0"
    data_type: TYPE_FP32
    dims: [1000]
  }
]

dynamic_batching {
  preferred_batch_size: [8, 16, 32]
  max_queue_delay_microseconds: 100
}

instance_group [
  {
    count: 2
    kind: KIND_GPU
    gpus: [0]
  }
]
EOF
```

***

## Paso 5 — Desplegar un Modelo ONNX

### Exportar a ONNX

```python
import torch
import torchvision
import torch.onnx

model = torchvision.models.resnet50(pretrained=True)
model.eval()

dummy_input = torch.randn(1, 3, 224, 224)

torch.onnx.export(
    model,
    dummy_input,
    "/tmp/resnet50.onnx",
    opset_version=13,
    input_names=["images"],
    output_names=["logits"],
    dynamic_axes={
        "images": {0: "batch_size"},
        "logits": {0: "batch_size"}
    }
)
```

### Configuración ONNX

```bash
mkdir -p /models/resnet50_onnx/1
scp -P <port> /tmp/resnet50.onnx root@<clore-host>:/models/resnet50_onnx/1/model.onnx

cat > /models/resnet50_onnx/config.pbtxt << 'EOF'
name: "resnet50_onnx"
platform: "onnxruntime_onnx"
max_batch_size: 32

input [
  {
    name: "images"
    data_type: TYPE_FP32
    dims: [3, 224, 224]
  }
]

output [
  {
    name: "logits"
    data_type: TYPE_FP32
    dims: [1000]
  }
]

dynamic_batching {
  preferred_batch_size: [8, 16, 32]
  max_queue_delay_microseconds: 100
}
EOF
```

***

## Paso 6 — Desplegar un Backend Personalizado en Python

Para modelos que no encajan en backends estándar (preprocesamiento personalizado, lógica de ensamble):

```bash
mkdir -p /models/custom_model/1

cat > /models/custom_model/1/model.py << 'EOF'
import triton_python_backend_utils as pb_utils
import numpy as np
import torch

class TritonPythonModel:
    def initialize(self, args):
        self.model = torch.nn.Linear(10, 5).cuda()
        self.model.eval()
    
    def execute(self, requests):
        responses = []
        for request in requests:
            input_tensor = pb_utils.get_input_tensor_by_name(request, "INPUT")
            input_np = input_tensor.as_numpy()
            
            with torch.no_grad():
                inp = torch.from_numpy(input_np).float().cuda()
                out = self.model(inp).cpu().numpy()
            
            output_tensor = pb_utils.Tensor("OUTPUT", out.astype(np.float32))
            responses.append(pb_utils.InferenceResponse(output_tensors=[output_tensor]))
        
        return responses
    
    def finalize(self):
        pass
EOF

cat > /models/custom_model/config.pbtxt << 'EOF'
name: "custom_model"
backend: "python"
max_batch_size: 64

input [
  {
    name: "INPUT"
    data_type: TYPE_FP32
    dims: [10]
  }
]

output [
  {
    name: "OUTPUT"
    data_type: TYPE_FP32
    dims: [5]
  }
]
EOF
```

***

## Paso 7 — Iniciar Triton y Probar

### Iniciar Triton Server

```bash
# Iniciar (si usas el CMD del Dockerfile, se inicia automáticamente)
tritonserver \
    --model-repository=/models \
    --http-port=8000 \
    --grpc-port=8001 \
    --metrics-port=8002 \
    --log-verbose=0 &

# Esperar a que el servidor esté listo
sleep 5
curl -s http://localhost:8000/v2/health/ready
# Esperado: {"live": true}
```

### Comprobar modelos disponibles

```bash
curl http://<clore-host>:<public-8000>/v2/models
```

### Ejecutar inferencia vía HTTP

```python
import tritonclient.http as httpclient
import numpy as np

client = httpclient.InferenceServerClient(
    url="<clore-host>:<public-port-8000>",
    ssl=False
)

# Comprobar la salud del servidor
print("Servidor listo:", client.is_server_ready())
print("Modelo listo:", client.is_model_ready("resnet50_onnx"))

# Crear entrada
image = np.random.rand(1, 3, 224, 224).astype(np.float32)
input_tensor = httpclient.InferInput("images", image.shape, "FP32")
input_tensor.set_data_from_numpy(image)

# Ejecutar inferencia
outputs = [httpclient.InferRequestedOutput("logits")]
response = client.infer("resnet50_onnx", [input_tensor], outputs=outputs)

logits = response.as_numpy("logits")
predicted_class = np.argmax(logits[0])
print(f"Clase predicha: {predicted_class}")
```

### Ejecutar inferencia vía gRPC

```python
import tritonclient.grpc as grpcclient
import numpy as np

client = grpcclient.InferenceServerClient(
    url="<clore-host>:<public-port-8001>"
)

image = np.random.rand(1, 3, 224, 224).astype(np.float32)
input_tensor = grpcclient.InferInput("images", image.shape, "FP32")
input_tensor.set_data_from_numpy(image)

outputs = [grpcclient.InferRequestedOutput("logits")]
response = client.infer("resnet50_onnx", [input_tensor], outputs=outputs)

logits = response.as_numpy("logits")
print(f"Forma de la salida: {logits.shape}")
```

***

## Monitorización con Prometheus

Triton expone métricas en el puerto 8002:

```bash
curl http://<clore-host>:<public-port-8002>/metrics
```

Métricas clave:

```
# Rendimiento de inferencia
nv_inference_request_success{model="resnet50_onnx", version="1"}
# Tiempo promedio de inferencia
nv_inference_compute_infer_duration_us{model="resnet50_onnx", version="1"}
# Utilización de la GPU
nv_gpu_utilization{gpu_uuid="..."}
# Memoria de la GPU
nv_gpu_memory_used_bytes{gpu_uuid="..."}
```

***

## Configuración de Batching Dinámico

```protobuf
dynamic_batching {
  preferred_batch_size: [4, 8, 16, 32]
  max_queue_delay_microseconds: 5000
  preserve_ordering: true
  
  priority_levels: 3
  default_priority_level: 2
  default_queue_policy {
    timeout_action: REJECT
    default_timeout_microseconds: 10000
    allow_timeout_override: true
    max_queue_size: 100
  }
}
```

***

## Solución de problemas

### Fallo al Cargar el Modelo

```
Error al cargar el modelo: no se pudo encontrar el archivo del modelo
```

**Solución:** Comprueba la estructura de directorios y los permisos:

```bash
ls -la /models/resnet50/1/
# Debe contener model.pt (PyTorch) o model.onnx (ONNX)
chmod -R 755 /models/
```

### Incompatibilidad de CUDA

**Solución:** Haz coincidir la versión de la imagen Triton con tu controlador CUDA:

```bash
nvidia-smi  # Anota la versión de CUDA
# Usa la etiqueta de tritonserver correspondiente, p. ej., 23.10 para CUDA 12.2
```

### Puerto No Alcanzable

**Solución:** Verifica que los tres puertos (8000, 8001, 8002) estén reenviados en Clore.ai. Prueba cada uno:

```bash
curl http://<host>:<port>/v2/health/live
```

### OOM Durante la Carga del Modelo

**Solución:** Reduce el conteo de instancias o usa instancias CPU para algunos modelos:

```protobuf
instance_group [
  {
    count: 1       # Reducir desde el valor por defecto
    kind: KIND_GPU
  }
]
```

***

## Estimación de Costos

| GPU       | VRAM  | Precio estimado | Rendimiento (ResNet50) |
| --------- | ----- | --------------- | ---------------------- |
| RTX 3080  | 10 GB | \~$0.10/hr      | \~500 req/sec          |
| RTX 4090  | 24 GB | \~$0.35/hr      | \~1500 req/sec         |
| A100 40GB | 40 GB | \~$0.80/hr      | \~3000 req/sec         |
| H100      | 80 GB | \~$2.50/hr      | \~8000 req/sec         |

***

## Recursos Útiles

* [Triton GitHub](https://github.com/triton-inference-server/server)
* [Registro de Contenedores NGC](https://catalog.ngc.nvidia.com/orgs/nvidia/containers/tritonserver)
* [Bibliotecas Cliente de Triton](https://github.com/triton-inference-server/client)
* [Referencia de Configuración de Modelos de Triton](https://docs.nvidia.com/deeplearning/triton-inference-server/user-guide/docs/user_guide/model_configuration.html)
* [Backend Python de Triton](https://github.com/triton-inference-server/python_backend)
* [Analizador de Rendimiento de Triton](https://github.com/triton-inference-server/client/blob/main/src/c%2B%2B/perf_analyzer/README.md)

***

## Recomendaciones de GPU en Clore.ai

| Caso de uso              | GPU recomendada | Coste estimado en Clore.ai |
| ------------------------ | --------------- | -------------------------- |
| Desarrollo/Pruebas       | RTX 3090 (24GB) | \~$0.12/gpu/hr             |
| Inferencia en Producción | RTX 4090 (24GB) | \~$0.70/gpu/hr             |
| Modelos grandes (70B+)   | A100 80GB       | \~$1.20/gpu/hr             |

> 💡 Todos los ejemplos en esta guía pueden desplegarse en [Clore.ai](https://clore.ai/marketplace) servidores GPU. Navega las GPUs disponibles y alquila por hora — sin compromisos, acceso root completo.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.clore.ai/guides/guides_v2-es/mlops-y-despliegue/triton-inference-server.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
