> For the complete documentation index, see [llms.txt](https://docs.clore.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.clore.ai/guides/guides_v2-zh/mlops-yu-bu-shu/triton-inference-server.md).

# Triton Inference Server

**NVIDIA Triton 推理服务器** 是一个适用于生产级的开源推理服务平台，支持几乎所有主流机器学习框架。Triton 旨在实现高吞吐、低延迟的服务，可处理 PyTorch、TensorFlow、ONNX、TensorRT、OpenVINO 等模型——全部由单个服务器进程提供。将其部署在 Clore.ai 的 GPU 云上，可获得可扩展、成本高效的推理基础设施。

***

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

Triton 是 NVIDIA 针对大规模提供 ML 模型服务这一挑战给出的答案：

* **多框架：** 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 显卡 | RTX 4090 / A100 |
| 内存     | 16 GB                     | 32 GB           |
| 存储     | 20 GB                     | 50 GB           |

{% hint style="info" %}
Triton 也支持仅 CPU 推理，适用于非 CUDA 工作负载。对于不需要 GPU 的批处理任务，可使用 Docker 镜像的 `仅 CPU` 版本来节省成本。
{% 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("Server ready:", client.is_server_ready())
print("Model ready:", 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
  }
}
```

***

## 故障排查

### 模型加载失败

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

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

```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       | 显存    | 预估价格                              | 吞吐量（ResNet50） |
| --------- | ----- | --------------------------------- | ------------- |
| RTX 3080  | 10 GB | $0.05–0.19/小时                     | \~500 请求/秒    |
| RTX 4090  | 24 GB | $0.14–0.42/小时                     | \~1500 请求/秒   |
| A100 40GB | 40 GB | [裸机](https://clore.ai/bare-metal) | \~3000 请求/秒   |
| H100      | 80 GB | \~$1.04/小时                        | \~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)

***

## Clore.ai GPU 推荐

| 使用场景      | 推荐 GPU         | Clore.ai 预计成本                     |
| --------- | -------------- | --------------------------------- |
| 开发/测试     | RTX 3090（24GB） | $0.07–0.21/gpu/hr                 |
| 生产级推理     | RTX 4090（24GB） | $0.14–0.42/gpu/hr                 |
| 大模型（70B+） | A100 80GB      | [裸机](https://clore.ai/bare-metal) |

> 💡 本指南中的所有示例都可以部署在 [Clore.ai](https://clore.ai/marketplace) GPU 服务器上。浏览可用 GPU 并按小时租用——无需承诺，拥有完整 root 访问权限。


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## 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, and the optional `goal` query parameter:

```
GET https://docs.clore.ai/guides/guides_v2-zh/mlops-yu-bu-shu/triton-inference-server.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
