# Triton Inference Server

**NVIDIA Triton 推理服务器** 是一个面向生产、开源的推理服务平台，支持几乎所有主流的机器学习框架。Triton 为高吞吐、低延迟的服务而设计，能够处理 PyTorch、TensorFlow、ONNX、TensorRT、OpenVINO 等——全部在单个服务器进程中运行。将其部署在 Clore.ai 的 GPU 云上以获得可扩展且具有成本效益的推理基础设施。

***

## 什么是 Triton 推理服务器？

Triton 是 NVIDIA 针对大规模部署机器学习模型服务这一挑战的解决方案：

* **多框架：** PyTorch、TensorFlow、TensorRT、ONNX、OpenVINO、Python 自定义后端
* **并发执行：** 多个模型，每个 GPU 上多个实例
* **动态批处理：** 自动对请求进行批处理以提高吞吐量
* **gRPC + HTTP：** 开箱即用的行业标准协议
* **指标：** 兼容 Prometheus 的指标端点
* **模型仓库：** 基于文件系统的模型管理

**使用的端口：**

| 端口   | 协议   | 用途            |
| ---- | ---- | ------------- |
| 8000 | HTTP | REST 推理 API   |
| 8001 | gRPC | gRPC 推理 API   |
| 8002 | HTTP | Prometheus 指标 |

***

## 先决条件

| 要求       | 最低                         | 推荐              |
| -------- | -------------------------- | --------------- |
| GPU 显存   | 8 GB                       | 16–24 GB        |
| GPU      | 任何支持 CUDA 11+ 的 NVIDIA GPU | RTX 4090 / A100 |
| 内存 (RAM) | 16 GB                      | 32 GB           |
| 存储       | 20 GB                      | 50 GB           |

{% hint style="info" %}
Triton 也支持仅 CPU 的推理以处理无 CUDA 的工作负载。对于不需要 GPU 的批处理作业，可使用 `仅 CPU` 变体的 Docker 镜像以节省成本。
{% endhint %}

***

## 步骤 1 — 在 Clore.ai 上租用 GPU

1. 登录到 [clore.ai](https://clore.ai).
2. 点击 **市场** 并筛选 VRAM ≥ 16 GB。
3. 选择一台服务器并点击 **配置**.
4. 设置 Docker 镜像： **`nvcr.io/nvidia/tritonserver:24.01-py3`**
   * （将 `24.01` 替换为最新版本 — 请检查 [NGC 目录](https://catalog.ngc.nvidia.com/orgs/nvidia/containers/tritonserver))
5. 设置开放端口： `22` （SSH）， `8000` （HTTP）， `8001` （gRPC）， `8002` （指标）。
6. 点击 **租用**.

{% hint style="warning" %}
Triton Docker 镜像较大（约 15–20 GB）。首次启动时拉取需 3–5 分钟。后续启动较快。
{% endhint %}

***

## 步骤 2 — 自定义 Dockerfile（包含 SSH）

官方 Triton 镜像不包含 SSH 服务。使用此 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/*

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

# 安装 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
```

***

## 步骤 3 — 了解模型仓库

Triton 从 **模型仓库** 加载模型 — 一个具有特定结构的目录：

```
/models/
├── model_name_1/
│   ├── config.pbtxt          # 模型配置
│   ├── 1/                    # 版本 1
│   │   └── model.pt          # 模型文件
│   └── 2/                    # 版本 2（可选）
│       └── model.pt
├── model_name_2/
│   ├── config.pbtxt
│   └── 1/
│       └── model.onnx
```

每个模型需要：

1. 一个以模型名称命名的目录
2. 一个 `config.pbtxt` 配置文件
3. 至少一个版本子目录（例如， `1/`）并包含模型文件

***

## 步骤 4 — 部署 PyTorch 模型

### 导出模型为 TorchScript

```python
import torch
import torchvision

# 加载预训练的 ResNet50
model = torchvision.models.resnet50(pretrained=True)
model.eval()

# 导出为 TorchScript
example_input = torch.randn(1, 3, 224, 224)
traced_model = torch.jit.trace(model, example_input)

# 保存
traced_model.save("/tmp/resnet50.pt")
print("模型导出成功")
```

### 设置模型仓库

```bash
# SSH 登录到你的 Clore.ai 实例
ssh root@<clore-host> -p <port>

# 创建目录结构
mkdir -p /models/resnet50/1

# 上传模型
#（从你的本地机器）
scp -P <port> /tmp/resnet50.pt root@<clore-host>:/models/resnet50/1/model.pt
```

### 创建 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
```

***

## 步骤 5 — 部署 ONNX 模型

### 导出为 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"}
    }
)
```

### 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
```

***

## 步骤 6 — 部署 Python 自定义后端

对于不适合标准后端的模型（自定义预处理、集成逻辑）：

```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
```

***

## 步骤 7 — 启动 Triton 并测试

### 启动 Triton 服务器

```bash
# 启动（如果使用 Dockerfile 的 CMD，则会自动启动）
tritonserver \
    --model-repository=/models \
    --http-port=8000 \
    --grpc-port=8001 \
    --metrics-port=8002 \
    --log-verbose=0 &

# 等待服务器就绪
sleep 5
curl -s http://localhost:8000/v2/health/ready
# 预期：{"live": true}
```

### 检查可用模型

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

### 通过 HTTP 运行推理

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

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

# 检查服务器健康状况
print("服务器就绪：", client.is_server_ready())
print("模型就绪：", client.is_model_ready("resnet50_onnx"))

# 创建输入
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)

# 运行推理
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"预测类别：{predicted_class}")
```

### 通过 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"输出形状：{logits.shape}")
```

***

## 使用 Prometheus 进行监控

Triton 在端口 8002 暴露指标：

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

关键指标：

```
# 推理吞吐量
nv_inference_request_success{model="resnet50_onnx", version="1"}
# 平均推理时间
nv_inference_compute_infer_duration_us{model="resnet50_onnx", version="1"}
# GPU 利用率
nv_gpu_utilization{gpu_uuid="..."}
# GPU 内存
nv_gpu_memory_used_bytes{gpu_uuid="..."}
```

***

## 动态批处理配置

```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
  }
}
```

***

## RuntimeError: CUDA out of memory

### 模型加载失败

```
加载模型失败：无法找到模型文件
```

**解决方案：** 检查目录结构和权限：

```bash
ls -la /models/resnet50/1/
# 必须包含 model.pt（PyTorch）或 model.onnx（ONNX）
chmod -R 755 /models/
```

### CUDA 不兼容

**解决方案：** 将 Triton 镜像版本与您的 CUDA 驱动匹配：

```bash
nvidia-smi  # 查看 CUDA 版本
# 使用匹配的 tritonserver 标签，例如针对 CUDA 12.2 的 23.10
```

### 端口不可访问

**解决方案：** 确认在 Clore.ai 中已转发所有三个端口（8000、8001、8002）。逐个测试：

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

### 模型加载时 OOM（内存不足）

**解决方案：** 减少实例数量或对部分模型使用 CPU 实例：

```protobuf
instance_group [
  {
    count: 1       # 从默认值减少
    kind: KIND_GPU
  }
]
```

***

## 成本估算

| GPU       | 显存 (VRAM) | 预计价格       | 吞吐量（ResNet50） |
| --------- | --------- | ---------- | ------------- |
| RTX 3080  | 10 GB     | 约 $0.10/小时 | 约 500 请求/秒    |
| RTX 4090  | 24 GB     | 约 $0.35/小时 | 约 1500 请求/秒   |
| A100 40GB | 40 GB     | 约 $0.80/小时 | 约 3000 请求/秒   |
| H100      | 80 GB     | 约 $2.50/小时 | 约 8000 请求/秒   |

***

## 有用的资源

* [Triton GitHub](https://github.com/triton-inference-server/server)
* [NGC 容器注册表](https://catalog.ngc.nvidia.com/orgs/nvidia/containers/tritonserver)
* [Triton 客户端库](https://github.com/triton-inference-server/client)
* [Triton 模型配置参考](https://docs.nvidia.com/deeplearning/triton-inference-server/user-guide/docs/user_guide/model_configuration.html)
* [Triton Python 后端](https://github.com/triton-inference-server/python_backend)
* [Triton 性能分析器](https://github.com/triton-inference-server/client/blob/main/src/c%2B%2B/perf_analyzer/README.md)

***

## 推荐 GPU

| 在 Clore.ai 上的预估费用 | 开发/测试             | RTX 3090（24GB） |
| ----------------- | ----------------- | -------------- |
| \~$0.12/每 GPU/每小时 | 生产                | RTX 4090（24GB） |
| 生产级推理             | 大规模               | A100 80GB      |
| 大型模型（70B+）        | 💡 本指南中的所有示例均可部署在 | Clore.ai       |

> GPU 服务器上。浏览可用 GPU 并按小时租用 — 无需承诺，提供完整的 root 访问权限。 [Clore.ai](https://clore.ai/marketplace) GPU 服务器。浏览可用 GPU 并按小时租用 — 无需承诺，提供完整的 root 访问权限。
