阶段四 · 模型部署与 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_sizemax_queue_delay_microseconds,用压测工具观察 batch 聚合效果
  3. 模型版本切换: 在模型仓库中放两个版本,分别调用 versions/1versions/2,验证返回结果不同
  4. 健康检查集成: 写一个 Go 程序,定期调用 Triton 的 gRPC 健康检查接口,打印模型状态
  5. 模型热加载: Triton 运行中,新建一个版本目录并放入模型文件,观察 Triton 日志中的自动加载过程