阶段四 · 模型部署与 Go 集成

推理服务架构

一句话总结

模型导出为 ONNX 只是第一步--要让它在生产环境稳定服务,还需要一个推理服务器来管理模型加载, 请求调度, 版本切换和健康检查.
本篇对比三种主流方案,选出最适合 Go 后端集成的架构.

前置回顾

第 15 篇把 PyTorch 模型导出为 ONNX 格式,验证了导出文件的正确性和推理加速效果.
现在的问题是:这个 .onnx 文件怎么在线上跑起来,对外提供 API?

为什么不能直接在 Go 里加载 ONNX

最简单的方案:Go 服务启动时用 onnxruntime-go 直接加载模型文件,请求来了就推理.

这在 demo 阶段可行,但生产环境有几个致命问题:

  1. 模型更新要重启服务 -- 新版本模型上线意味着 Go 进程重启,影响在线流量
  2. 资源隔离困难 -- 模型推理是 CPU/GPU 密集型,和业务逻辑挤在同一进程,互相争抢资源
  3. 无法独立扩缩容 -- 推理负载高时,只能整个 Go 服务扩容,浪费业务逻辑部分的资源
  4. 多模型管理复杂 -- 群聊审核可能同时需要情感模型, 意图模型, 毒性模型,全部塞进一个进程难以维护

解决方案:把模型推理拆成独立服务,Go 业务层通过 gRPC/HTTP 调用.

拆分前:                          拆分后:

┌─────────────────────┐ ┌──────────────┐ gRPC ┌──────────────────┐
│ Go 服务 │ │ Go 服务 │─────────────→│ 推理服务(GPU) │
│ ┌───────────────┐ │ │ (业务逻辑) │ │ ┌──────────────┐ │
│ │ 业务逻辑 │ │ └──────────────┘ │ │ 模型 A v2 │ │
│ ├───────────────┤ │ │ │ 模型 B v1 │ │
│ │ 模型推理 │ │ │ │ 模型 C v3 │ │
│ │ (ONNX Runtime)│ │ │ └──────────────┘ │
│ └───────────────┘ │ └──────────────────┘
└─────────────────────┘
独立部署,独立扩容,独立更新模型

推理服务 = 数据库. 没有人会把 MySQL 嵌入到 Go 进程里(虽然 SQLite 能做到). 数据库独立部署,应用通过网络协议访问. 模型推理也是同理--计算密集, 有状态(模型文件), 需要独立管理的基础设施.

三种主流推理服务器

NVIDIA Triton Inference Server

专为高性能推理设计的服务器,支持多种模型格式:

维度 说明
支持格式 ONNX, TensorRT, PyTorch, TensorFlow, Python 自定义
通信协议 gRPC + HTTP/REST
核心能力 Dynamic batching, 模型并发执行, GPU 实例分组
模型管理 文件系统目录结构,支持热加载和版本管理
适用场景 有 GPU,追求极致吞吐的在线推理

Triton 的模型仓库结构:

model_repository/
├── sentiment_model/
│ ├── config.pbtxt ← 模型配置(输入输出 shape, batch 策略)
│ ├── 1/ ← 版本 1
│ │ └── model.onnx
│ └── 2/ ← 版本 2(最新)
│ └── model.onnx
├── intent_model/
│ ├── config.pbtxt
│ └── 1/
│ └── model.onnx
// config.pbtxt 示例
name: "sentiment_model"
platform: "onnxruntime_onnx"
max_batch_size: 64

input [
{
name: "input_ids"
data_type: TYPE_INT64
dims: [ -1 ] // 动态长度
},
{
name: "attention_mask"
data_type: TYPE_INT64
dims: [ -1 ]
}
]

output [
{
name: "logits"
data_type: TYPE_FP32
dims: [ 3 ] // 3 分类
}
]

dynamic_batching {
preferred_batch_size: [ 8, 16, 32 ]
max_queue_delay_microseconds: 5000 // 最多等 5ms 凑 batch
}

TorchServe

PyTorch 官方推理服务,适合纯 PyTorch 生态:

维度 说明
支持格式 TorchScript, eager mode PyTorch
通信协议 HTTP/REST (gRPC 支持有限)
核心能力 自定义 handler, 模型归档(.mar), 管理 API
模型管理 .mar 打包文件,通过 Management API 注册/删除
适用场景 纯 PyTorch 模型,不需要 ONNX 转换

TorchServe 与 Go 集成的局限

TorchServe 的 gRPC 支持不如 Triton 成熟,主要依赖 HTTP. 对于追求低延迟的 Go 后端,HTTP 序列化开销比 gRPC 高不少. 如果你的模型已经导出为 ONNX,TorchServe 不是最佳选择.

FastAPI + ONNX Runtime

自己写一个 Python Web 服务,用 ONNX Runtime 加载模型:

from fastapi import FastAPI
from onnxruntime import InferenceSession
import numpy as np

app = FastAPI()
session = InferenceSession("model.onnx", providers=["CUDAExecutionProvider"])

@app.post("/predict")
async def predict(input_ids: list[int], attention_mask: list[int]):
outputs = session.run(
None,
{
"input_ids": np.array([input_ids], dtype=np.int64),
"attention_mask": np.array([attention_mask], dtype=np.int64),
},
)
logits = outputs[0][0]
return {"label": int(np.argmax(logits)), "scores": logits.tolist()}
维度 说明
支持格式 ONNX (通过 onnxruntime)
通信协议 HTTP/REST, 可自行加 gRPC
核心能力 完全自定义,灵活
模型管理 自己实现
适用场景 快速验证, 简单部署, 团队 Python 能力强

自建推理服务的隐性成本

看起来最简单,但 dynamic batching, 模型热更新, 健康检查, GPU 内存管理这些全要自己写. 适合 PoC 阶段,生产环境建议用 Triton.

方案对比与选型

维度 Triton TorchServe FastAPI + ORT
gRPC 支持 原生,成熟 实验性 需自建
Dynamic Batching 内置,可配置 内置 需自建
模型热更新 文件目录监听 Management API 需自建
GPU 利用率 最优(实例分组) 中等 取决于实现
学习成本 中(配置多) 低 低(但后期高)
Go 集成友好度 高(标准 gRPC) 中(HTTP 为主) 中

群聊审核场景的推荐方案: Triton Inference Server.

理由:

  1. 原生 gRPC 接口,Go 客户端直接调用
  2. Dynamic batching 天然适合群消息的突发流量
  3. 多模型管理(情感 + 意图 + 毒性)开箱即用
  4. 模型版本管理内置,A/B 测试只需放两个版本目录

模型服务模式:在线 vs 批量

在线推理 (Online Inference)

请求到达时实时推理,延迟敏感:

用户发消息 → Go 服务接收 → gRPC 调用推理服务 → 返回审核结果 → 决定是否放行
↑
要求 p99 < 50ms

适用场景:

  • 消息发送前拦截(同步审核)
  • 实时风险预警

批量推理 (Batch Inference)

积攒一批数据后统一推理,吞吐优先:

消息队列积攒 → 每 100 条或每 5s 触发 → 批量推理 → 结果写回数据库 → 异步处理

适用场景:

  • 历史消息回扫
  • 离线训练数据标注
  • 非实时的内容分析报表

混合模式

群聊审核的最佳实践是混合:

┌─────────────────────────────────────────────────────────┐
│ 消息到达 │
│ │ │
│ ┌──────────┴──────────┐ │
│ ▼ ▼ │
│ 规则引擎(同步) 模型推理(异步) │
│ - 关键词黑名单 - 情感分析 │
│ - 正则匹配 - 意图识别 │
│ - 用户画像 - 上下文理解 │
│ │ │ │
│ ▼ ▼ │
│ 明确违规 → 立即拦截 模型判定 → 延迟处理/人工复审 │
└─────────────────────────────────────────────────────────┘

规则引擎命中率高的场景(如明显脏话)直接拦截,不走模型. 模型处理需要语义理解的灰色地带.

扩容策略

水平扩容

推理服务无状态(模型文件在启动时加载到内存),可以直接水平扩:

# Kubernetes HPA 配置示例
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: triton-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: triton-inference
minReplicas: 2
maxReplicas: 8
metrics:
- type: Pods
pods:
metric:
name: triton_queue_duration_us # 排队时间作为扩容指标
target:
type: AverageValue
averageValue: "5000" # 排队超过 5ms 就扩容

GPU 实例分组

Triton 支持在同一 GPU 上运行多个模型实例:

GPU 0 (16GB):
├── sentiment_model instance_0 (占用 ~2GB)
├── sentiment_model instance_1 (占用 ~2GB)
├── intent_model instance_0 (占用 ~1GB)
└── toxicity_model instance_0 (占用 ~3GB)
剩余 ~8GB 给 dynamic batching 缓冲

小模型(DistilBERT 级别, ~200MB)在单张 GPU 上可以跑多个实例并发处理请求.

健康检查与模型版本管理

健康检查

Triton 提供三级健康检查:

端点 含义 用途
/v2/health/live 进程存活 K8s liveness probe
/v2/health/ready 模型已加载完成,可以接收请求 K8s readiness probe
/v2/models/{name}/ready 指定模型就绪 精细化检查
// Go 侧健康检查示例
func checkModelReady(ctx context.Context, conn *grpc.ClientConn, modelName string) error {
client := grpc_generated.NewGRPCInferenceServiceClient(conn)
resp, err := client.ModelReady(ctx, &grpc_generated.ModelReadyRequest{
Name: modelName,
})
if err != nil {
return fmt.Errorf("health check failed: %w", err)
}
if !resp.Ready {
return fmt.Errorf("model %s not ready", modelName)
}
return nil
}

模型版本管理

Triton 的版本策略通过 config.pbtxt 配置:

// 只用最新版本
version_policy: { latest { num_versions: 1 } }

// 指定版本
version_policy: { specific { versions: [1, 3] } }

// 所有版本都可用(用于 A/B 测试)
version_policy: { all {} }

上线新版本的流程:

1. 训练完成,导出 model_v3.onnx
2. 拷贝到模型仓库: model_repository/sentiment_model/3/model.onnx
3. Triton 检测到新目录,自动加载(或调用 model control API)
4. 验证 /v2/models/sentiment_model/versions/3/ready
5. 切换流量到 v3(通过 Go 侧指定 model_version)
6. 观察指标,确认无误后删除旧版本目录

版本回滚

如果新版本模型效果下降,只需在 Go 客户端把请求的 model_version 改回旧版本号. 不需要重启推理服务,不需要重新部署. 这是模型版本管理的核心价值.

生产注意事项

模型加载时间

大模型首次加载可能需要 10-30 秒(读文件 + GPU 内存分配 + 图优化). readiness probe 的初始延迟要设够. 如果用 K8s,initialDelaySeconds 至少设 60 秒.

GPU 内存泄漏

某些 ONNX Runtime 版本在长时间运行后会出现 GPU 内存缓慢增长. 建议监控 gpu_memory_used 指标,设置告警阈值. 极端情况下可以定期重启推理容器(滚动更新方式,不中断服务).

模型文件存储

模型文件不要放在容器镜像里(镜像太大,拉取慢). 推荐用 PV/对象存储 + init container 的方式:容器启动时从 S3/MinIO 下载模型到本地,然后启动 Triton.

快速回顾

  • 推理服务独立部署: 模型推理和业务逻辑分离,各自扩缩容
  • Triton 是首选: 原生 gRPC, dynamic batching, 多模型管理, Go 集成友好
  • 在线 + 批量混合: 规则引擎兜底同步拦截,模型处理语义灰区
  • 版本管理: 目录结构管理版本,Go 侧指定版本号即可切换/回滚
  • 健康检查三级: live(存活) → ready(就绪) → model ready(模型级)

动手练习

  1. 部署 Triton: 用 Docker 启动 Triton,加载第 15 篇导出的 ONNX 模型,验证 /v2/health/ready 返回 200
  2. 配置 dynamic batching: 修改 config.pbtxt,设置 preferred_batch_size 和 max_queue_delay_microseconds,用压测工具观察 batch 聚合效果
  3. 模型版本切换: 在模型仓库中放两个版本,分别调用 versions/1 和 versions/2,验证返回结果不同
  4. 健康检查集成: 写一个 Go 程序,定期调用 Triton 的 gRPC 健康检查接口,打印模型状态
  5. 模型热加载: Triton 运行中,新建一个版本目录并放入模型文件,观察 Triton 日志中的自动加载过程