4. 模型仓库与版本管理

在 TensorRT 多模型推理服务中,模型仓库不是简单的“放模型文件的目录”,而是服务启动、加载、路由和回滚所依赖的唯一事实来源。一个可运维的模型仓库需要同时回答四个问题:模型文件放在哪里、每个模型对应什么版本、服务如何知道该加载哪个版本、出问题时如何快速切回旧版本。本节围绕这四个问题展开,并给出可直接落地的目录结构、元数据字段和版本切换策略。

一、模型文件组织:按模型名与版本号分层

TensorRT 推理服务通常以“模型名 + 版本号”作为最小加载单元。推荐采用如下目录结构,每个模型一个子目录,版本号使用纯数字或语义化版本,避免使用 latest 这类浮动标签:

model_repository/
├── resnet50/
│   ├── 1/
│   │   ├── model.plan          # TensorRT 序列化引擎
│   │   └── config.pbtxt        # 模型配置(输入输出、batch、实例数)
│   ├── 2/
│   │   ├── model.plan
│   │   └── config.pbtxt
│   └── metadata.json           # 模型级元数据(可选)
├── bert_base/
│   ├── 1/
│   │   ├── model.plan
│   │   └── config.pbtxt
│   └── metadata.json
└── ensemble_ocr/
    └── 1/
        ├── model.plan
        └── config.pbtxt

这里的关键约定是:版本目录名即版本号,服务启动时扫描模型目录下的数字子目录,按版本号排序后加载。TensorRT 的 model.plan 是序列化引擎,与 GPU 架构、TensorRT 版本、CUDA 版本强绑定,因此不同环境应生成不同的 plan 文件,不能跨环境直接复制。若使用 ONNX 作为中间格式,可在版本目录中同时保留 model.onnx 和 model.plan,但服务实际加载的是 plan。

二、元数据描述:让服务与运维都能读懂模型

仅有文件不足以支撑多模型服务。元数据的作用是描述“这个版本是什么、由谁产生、依赖什么、如何路由”。推荐在模型级放置 metadata.json,在版本目录内放置 config.pbtxt 描述推理配置。一个可用的元数据示例:

{
  "model_name": "resnet50",
  "active_version": 2,
  "versions": {
    "1": {"status": "archived", "created_at": "2025-01-10T09:00:00Z",
          "precision": "fp16", "gpu_arch": "sm_86", "trt_version": "8.6.1"},
    "2": {"status": "active", "created_at": "2025-02-18T14:30:00Z",
          "precision": "int8", "gpu_arch": "sm_86", "trt_version": "8.6.1",
          "calibration": "entropy", "accuracy_drop": 0.004}
  },
  "input_schema": {"name": "input", "dtype": "FP32", "dims": [3, 224, 224]},
  "output_schema": {"name": "output", "dtype": "FP32", "dims": [1000]}
}

元数据中必须包含的字段包括:模型名、版本号、状态(active/archived/canary)、精度、GPU 架构、TensorRT 版本、创建时间、输入输出 schema。这些字段直接决定服务能否正确加载,以及回滚时能否快速判断旧版本是否仍兼容当前环境。例如,若当前 GPU 从 sm_86 升级到 sm_89,旧版本 plan 可能无法加载,此时回滚策略必须包含“重新生成 plan”这一分支,而不是简单切目录。

模型仓库版本管理与切换/回滚关系图 模型仓库 model_repository resnet50/ bert_base/ ensemble_ocr/ resnet50 版本目录 1/ model.plan + config.pbtxt 2/ model.plan + config.pbtxt metadata.json(active_version=2) 元数据关键字段 • model_name / version • status: active/canary/archived • precision / gpu_arch / trt_version • input_schema / output_schema • created_at / accuracy_drop TensorRT 推理服务加载流程 扫描模型目录 读取 metadata.json 校验版本兼容性 加载 active 版本 版本切换(灰度/全量) 1. 新版本目录写入 3/ 2. metadata 标记 3 为 canary,2 保持 active 3. 观察指标后,将 active_version 改为 3 回滚策略 1. 将 active_version 改回 2 2. 若 plan 不兼容,重新生成旧版 plan 3. 保留 3/ 目录用于问题复现

三、版本切换:从 canary 到 active 的原子操作

版本切换的核心是“先加载、后切流、再清理”。以 resnet50 从版本 2 切到版本 3 为例,推荐操作顺序如下:

  1. 将新引擎写入 resnet50/3/model.plan,并在 metadata.json 中新增版本 3,状态设为 canary。
  2. 服务热加载版本 3,但路由权重仍为 0,此时版本 2 继续承载全部流量。
  3. 将版本 3 的权重逐步提升到 5%、20%、50%,同时观察 P99 延迟、错误率和输出一致性。
  4. 确认无异常后,将 active_version 改为 3,版本 2 状态改为 archived,但目录保留。

这里的关键是切换动作必须原子化:active_version 的修改应通过一次写入完成,避免服务在读取时看到中间状态。若服务支持动态加载,可通过文件监听或配置中心推送触发;若不支持,则需重启服务,此时应保证重启期间旧版本仍可服务,通常通过多实例滚动重启实现。

四、回滚策略:快速、可验证、可复现

回滚不是“把目录改回去”这么简单。一个可用的回滚策略需要满足三个条件:快速(分钟级)、可验证(回滚后能确认服务恢复)、可复现(保留现场用于定位)。具体做法是:

  • 快速回滚:将 active_version 从 3 改回 2,并触发服务重新加载。若服务支持热切换,整个过程可在秒级完成;若需重启,则依赖多实例滚动,单实例回滚时间控制在 30 秒内。
  • 兼容性检查:回滚前检查旧版本 plan 的 gpu_arch 和 trt_version 是否与当前环境一致。若不一致,直接切目录会导致加载失败,此时应使用旧版本 ONNX 重新生成 plan,再执行切换。
  • 现场保留:回滚后不要删除新版本目录,而是将其状态标记为 failed,并保留对应的输入输出日志、精度对比数据,便于后续分析是精度问题、性能问题还是环境不兼容。
回滚时最容易忽略的是配置文件的版本一致性。config.pbtxt 中的 max_batch_size、输入维度、实例数必须与 plan 匹配。若只回滚 plan 而忘记回滚 config,服务可能加载成功但推理结果错误。建议将 config 与 plan 放在同一版本目录内,回滚时整体切换。

五、版本管理检查清单

在将模型仓库投入生产前,建议逐项核对以下内容,确保版本切换与回滚路径畅通:

检查项 合格标准 常见问题
版本目录命名 纯数字,按升序排列 使用 latest、v1.0 等非数字名导致排序错误
元数据完整性 包含 active_version、status、gpu_arch、trt_version 缺少 gpu_arch,跨环境回滚失败
plan 与 config 同目录 同一版本目录内同时存在 config 放在模型根目录,回滚时版本错配
回滚演练 每月至少一次,记录耗时与结果 从未演练,真实故障时才发现路径不通
旧版本保留策略 至少保留最近 2 个可用版本 只保留最新版,回滚无路可走

模型仓库与版本管理的本质,是把“模型文件”升级为“可被服务消费、可被运维切换、可被故障回滚”的工程资产。目录结构决定服务能否找到模型,元数据决定服务能否正确加载,切换与回滚策略决定故障时能否快速恢复。三者缺一不可,且必须在模型上线前就设计好,而不是等到出问题再补。