Skip to content

Latest commit

 

History

History
980 lines (741 loc) · 43.5 KB

deployment.zh-cn.md

File metadata and controls

980 lines (741 loc) · 43.5 KB

部署指南(Linux)

Mortred 仅面向 Linux。本文是快速开始的完整运维手册:架构、选型决策、 三条安装轨道的逐步流程、Profile 体系、权重与 Engine 管理、安全、升级回滚、监控与故障排查。

不支持边界:unsupported-boundaries.zh-cn.md。

第一小时主航道: 任选下方一种安装入口(§4–§7),然后反复执行 mortredctl next (token → 环回 listen → 启动 → 如需则 GPU pack prepare/calibrate → doctor --strict → 一次推理)。每次只打印一条 next:。

读完本文你可以做到:在一台干净的 Ubuntu 机器上,20 分钟内把 Mortred 跑起来并通过验收门禁。


目录


1. 架构总览

一套 Mortred 部署由一个控制面和一组模型进程组成,全部跑在同一台机器(或同一个容器)内:

flowchart LR
    subgraph 外部["外部客户端"]
        C["SDK / curl / 浏览器"]
    end

    subgraph 控制面["控制面(仅暴露这两个端口)"]
        GW["mortred-gateway :8080<br/>推理入口 · 鉴权 · 限流 · 路由"]
        SUP["mortred-supervisor :8787<br/>进程管理 · Web 控制台 · 管理 API"]
    end

    subgraph 数据面["数据面(仅 loopback,外部不可达)"]
        M1["mobilenetv2_server :9002"]
        M2["yolov8_server :9056"]
        M3["...更多模型进程"]
    end

    C -->|"POST /v1/models/{id}/infer"| GW
    GW -->|"internal token"| M1
    GW --> M2
    GW --> M3
    SUP -.->|"拉起/重启/探活"| M1
    SUP -.-> M2
    SUP -.-> M3
    C -->|"Web 控制台 / 管理 API"| SUP
Loading

关键设计:

设计点 说明
只有 2 个对外端口 网关 8080(推理流量)、supervisor 8787(管理 + Web 控制台)
模型进程仅绑 loopback 外部无法绕过网关直连模型;supervisor 注入 internal token
supervisor 管的是 pack 只拉起 conf/packs/*.toml 里列出的 catalog id;MORTRED_AUTOSTART=true 不会把整个 conf/server 树打满 GPU
fail-closed 缺推理/管理身份或 scrape token 时拒绝启动(含环回)。通配绑定需要 MORTRED_EXPOSE=docker 或 unsafe。TLS 在 Nginx(mortredctl init-edge)。

端口一览:

端口 进程 用途 鉴权
8080 mortred-gateway 推荐推理入口 POST /v1/models/{id}/infer;遗留 {server_uri}(如 /mortred_ai_server_v1/...)仍接受;/healthz、/metrics infer/jobs 要 Bearer;/healthz 公开;/metrics 一律要 MORTRED_METRICS_TOKEN(含环回)
8787 mortred-supervisor 管理 API /api/v1/*、Web 控制台 Bearer token
9002+ 各模型进程 仅 loopback internal token(含已注入时的 GET /metrics)

2. 五分钟选型

2.1 先选 Profile:cpu 还是 gpu?

flowchart TD
    A["机器上有 NVIDIA GPU 吗?"] -->|"nvidia-smi -L 成功"| GPU["gpu profile"]
    A -->|"无 GPU / 不确定"| CPU["cpu profile"]
    GPU --> G1["全部模型可用<br/>MNN-CUDA / ORT-CUDA / TensorRT"]
    CPU --> C1["精选 2 模型<br/>MNN-CPU,无 TensorRT"]
Loading
gpu(默认) cpu
推理后端 MNN-CUDA / ORT-CUDA / TensorRT MNN-CPU / ORT-CPU(TensorRT 编译排除)
硬件要求 NVIDIA GPU + 驱动,CUDA 12 + TensorRT 10 任意 x64 机器
可用模型 HTTP catalog(分类/检测/OCR(仅 dbnet 文字检测,无识别)/分割/抠图/增强/SuperPoint/深度/DINOv2/SAM AMG/扩散)。bench-only:CLIP、LightGlue、FastSAM、SAM prompt、MsOcrNet。无 MOT。RT-DETR 未实现。 精选集:mobilenetv2、resnet50
权重体积 全量 manifest(数十 GB) 精选子集(约 1 GB)
Engine 转换 本机为本 pack 转 engine(§10.2);全量 zoo 转换仍是可选项 不需要

不确定就选 cpu:选错的最坏结果是换个 profile 重来,数据面配置完全兼容。

2.2 再选轨道:Docker 还是 Tarball?

Docker 轨道 Tarball 轨道
适合 已有 Docker 习惯;想最快起服务 裸机/虚拟机生产;无 Docker;要 systemd 原生管理
安装内容 双镜像(ghcr.io/...:vX.Y.Z-cpu/-gpu) 自包含 tarball + install.sh + systemd 单元
升级 换镜像 tag mortredctl upgrade(原地,含 conf 备份)
环境隔离 容器级 依赖 apt 包,脚本自动安装
共同点 同一验收脚本 verify_deployment.sh;同一 profile 体系;同一 mortredctl 内核

三条入口(bootstrap / compose / tarball)共享同一个 mortredctl 内核,殊途同归于 mortredctl doctor 验收——不存在三条互相漂移的路径。


3. 前置要求

3.1 硬件

Profile 最低 推荐
cpu 2 核 / 4 GB / 10 GB 磁盘 8 核 / 16 GB / SSD
gpu 上述 + CUDA 12 显卡 RTX 3060+ / 8 GB 显存 / 50 GB 磁盘

3.2 操作系统与软件

项 要求 检查命令
操作系统 Ubuntu 20.04 / 22.04(x64) lsb_release -rs
curl 任意近期版本 curl --version
python3 ≥ 3.8(仅权重下载需要) python3 --version
docker + compose 仅 Docker 轨道 docker compose version
sudo 仅 Tarball 轨道安装时 —
NVIDIA 驱动 仅 gpu profile nvidia-smi

3.3 网络

  • 安装时:需要访问 GitHub Releases / Hugging Face(权重)。离线环境见 §9.4。
  • 运行时:compose / docker run 示例把 8080/8787 绑在宿主机 127.0.0.1。 局域网/公网暴露由你的反代决定(TLS 也在反代上终结)。没有反代就把这些 端口发到 0.0.0.0,Bearer 会明文传输。

4. 入口一:一行 Bootstrap

最快路径——检测硬件、选轨道、一路到底:

curl -fsSL https://raw.githubusercontent.com/MaybeShewill-CV/mortred_model_server/main/scripts/bootstrap.sh | bash

它会做什么:

  1. nvidia-smi -L 探测 → 推荐 gpu 或 cpu profile;
  2. 有 Docker → 打印 compose 轨道指令(见 §5);
  3. 无 Docker → 解析 GitHub 最新 release tag,下载 mortred_model_server-<version>-<profile>-linux-x64.tar.gz(没有 ...-latest-... 这种 tarball 文件名),有同名 .sha256 则校验,然后 sudo ./install.sh(见 §6);
  4. 还没有 Release,或该 profile 的包不存在 → 打印 [WARN] no versioned release tarball published yet for profile …,再打印 源码构建路径(见 §7)。这是文档里的轨道,不是隐藏失败。

预期输出(无 GPU + 有 Docker 的机器):

== Mortred bootstrap ==
  detected profile: cpu
== docker track ==
next:
  1. git clone https://github.com/MaybeShewill-CV/mortred_model_server.git && cd mortred_model_server
  2. python3 scripts/fetch_weights.py --profile cpu
  3. ./scripts/mortredctl_init-trust.sh && set -a && . conf/local/trust.env && set +a
  4. docker compose --profile cpu up -d
  5. curl -fs http://localhost:8787/api/v1/health

预期输出(无 GPU、无 Docker、尚无 GitHub Release):

== Mortred bootstrap ==
  detected profile: cpu
  [WARN] no versioned release tarball published yet for profile cpu
  [WARN] (GitHub latest tag missing or that profile asset is unpublished; there is no ...-latest-... tarball name)
== manual track ==
  1. git clone https://github.com/MaybeShewill-CV/mortred_model_server.git && cd mortred_model_server
  2. ./scripts/install_deps.sh --cpu --all
  3. cmake --preset full-cpu && cmake --build --preset full-cpu
  4. mortredctl init --profile cpu

bootstrap 本身刻意保持"薄":它只做检测与委托,不复制任何业务逻辑——升级 mortredctl 即升级全部入口。


5. 入口二:Docker Compose

5.1 安装(四步)

# ① 获取代码(compose 文件与权重脚本随仓库走)
git clone https://github.com/MaybeShewill-CV/mortred_model_server.git
cd mortred_model_server

# ② 拉取当前 profile 的权重子集(断点续传 + sha256 校验)
python3 scripts/fetch_weights.py --profile cpu     # GPU 机器换成 gpu

# ③ 三个互异 token(compose 对 scrape 也是 `:?`,缺了插值即失败)
./scripts/mortredctl_init-trust.sh
set -a && . conf/local/trust.env && set +a

# ④ 启动(本地构建镜像;首次约 10-25 分钟编译)
docker compose --profile cpu up -d      # GPU 机器换成 --profile gpu

compose 把 8080/8787 发布在宿主机 127.0.0.1 上。本机 localhost 客户端 不受影响;其他机器要访问必须前面加 TLS 反代,或改写 port mapping。

GPU 轨道需要 NVIDIA Container Toolkit(docker run --gpus all 可用即已装好)。

5.2 验证

# 健康探针(supervisor)
curl -fs http://localhost:8787/api/v1/health

# 网关健康(公开)+ 指标(含环回也要 scrape token)
curl -fs http://localhost:8080/healthz
curl -fs -H "Authorization: Bearer $MORTRED_METRICS_TOKEN" \
    http://localhost:8080/metrics | head -5

# 带鉴权的目录查询
curl -fs -H "Authorization: Bearer $MORTRED_API_TOKEN" \
    http://localhost:8787/api/v1/catalog | python3 -m json.tool | head -20

预期:/api/v1/health 返回 OK;catalog 列出当前 profile 与 pack 选中的模型。默认 demo pack 在 cpu 下通常只有 supervisor + gateway(无随包 *_cpu 产品;自建 cpu pack 见 §10.7,demo 为 TRT 种子)。

5.3 常用操作

操作 命令
查看日志 docker compose --profile cpu logs -f mortred-cpu
重启 docker compose --profile cpu restart
停止 docker compose --profile cpu down
升级镜像 docker compose --profile cpu pull && docker compose --profile cpu up -d
进入容器排查 docker exec -it mortred-cpu bash
GPU pack engine mortredctl prepare(§10.2);全量 MORTRED_AUTO_BUILD_ENGINES 仍默认关

5.4 使用预构建镜像(免本地编译)

docker pull ghcr.io/maybeshewill-cv/mortred_model_server:v0.1.0-cpu
docker run -d --name mortred \
  -p 127.0.0.1:8787:8787 -p 127.0.0.1:8080:8080 \
  -v "$PWD/weights:/opt/mortred/weights" \
  -e MORTRED_API_TOKEN=... -e MORTRED_GATEWAY_AUTH_TOKEN=... \
  -e MORTRED_METRICS_TOKEN=... \
  ghcr.io/maybeshewill-cv/mortred_model_server:v0.1.0-cpu

# GPU 运行时镜像(需要 NVIDIA Container Toolkit):
docker pull ghcr.io/maybeshewill-cv/mortred_model_server:v0.1.0-gpu
docker run -d --name mortred --gpus all \
  -p 127.0.0.1:8787:8787 -p 127.0.0.1:8080:8080 \
  -v "$PWD/weights:/opt/mortred/weights" \
  -e MORTRED_API_TOKEN=... -e MORTRED_GATEWAY_AUTH_TOKEN=... \
  -e MORTRED_METRICS_TOKEN=... \
  ghcr.io/maybeshewill-cv/mortred_model_server:v0.1.0-gpu

6. 入口三:Tarball + systemd

适合裸机生产:无 Docker 依赖,systemd 原生管理,重启自愈。

6.1 下载与校验

从 Releases 下载对应 profile 的包(以 v0.1.0 / cpu 为例):

VER=0.1.0
curl -fLO https://github.com/MaybeShewill-CV/mortred_model_server/releases/download/v$VER/mortred_model_server-$VER-cpu-linux-x64.tar.gz
curl -fLO https://github.com/MaybeShewill-CV/mortred_model_server/releases/download/v$VER/mortred_model_server-$VER-cpu-linux-x64.tar.gz.sha256
sha256sum -c mortred_model_server-$VER-cpu-linux-x64.tar.gz.sha256   # 必须输出 OK

tarball 内容:opt/mortred/(安装树)+ deploy/mortred-supervisor.service + install.sh + PROFILE 标记。 权重不打包(全量数十 GB)——安装后按 §9 拉取。

6.2 安装(root)

mkdir unpack
tar -xzf mortred_model_server-$VER-cpu-linux-x64.tar.gz -C unpack
cd unpack   # 平铺:根上就是 install.sh、opt/mortred/、deploy/
sudo ./install.sh

install.sh 逐步做什么(幂等,可重复执行):

步骤 内容
1 apt 安装运行时依赖(glog / OpenCV / openssl;gpu profile 另装 TensorRT/cuDNN 运行库)
2 部署安装树到 /opt/mortred;创建 mortred 系统用户
3 安装 systemd 单元并 enable(cpu profile 自动注入 MORTRED_PROFILE=cpu)
4 生成 /etc/mortred/supervisor.env 模板(600 权限)并打印后续步骤

6.3 配置 token 与权重

# ① 生成三个 token(覆盖 install.sh 留下的注释模板)
sudo /opt/mortred/bin/mortredctl.out init-trust --force --out /etc/mortred/supervisor.env
# 或自行 sudoedit 填入三个 MORTRED_*_TOKEN

# ② 拉权重(在安装树内执行)
cd /opt/mortred
sudo -u mortred python3 scripts/fetch_weights.py --profile cpu

6.4 启动与验证

sudo systemctl start mortred-supervisor
sudo systemctl status mortred-supervisor --no-pager    # active (running)
curl -fs http://127.0.0.1:8787/api/v1/health

systemd 单元要点:Restart=always、TimeoutStopSec=120(有序关停:先模型后网关)、EnvironmentFile=/etc/mortred/supervisor.env(600 权限)。


7. 源码构建

适合贡献者与需要自定义的场景。

7.1 依赖安装(版本矩阵 + sha256 锁定 + 幂等 stamp)

./scripts/install_deps.sh --check          # 查看当前 3rd_party 完整性
./scripts/install_deps.sh --all            # gpu 线(CUDA 12 / TensorRT 10.3)
./scripts/install_deps.sh --cpu --all      # cpu 线:MNN-CPU + ORT-CPU,完全不装 NVIDIA/TRT
sudo ./scripts/install_deps.sh --nvidia    # gpu 线的 CUDA/TRT/cuDNN(需 root,其余步骤无需)

离线安装:--offline DIR 使用预下载包目录;ORT tarball 强制 sha256 校验(缺哈希直接拒绝,绝不静默跳过)。

CMake configure 对源码树与 --check 对齐的已知坑 fail-closed:缺 libworkflow / libcrypto、ORT_API_VERSION 与 libonnxruntime.so.1.29.0 不一致、残留 libonnxruntime.so.1.18* (gpu 上还有残留 TensorRT 8)。FATAL 文案会给出对应的 install_deps 修复命令。

7.2 编译(preset 自带 profile)

cmake --preset full && cmake --build --preset full            # gpu 全量
cmake --preset full-cpu && cmake --build --preset full-cpu    # cpu 全量
cmake --preset tests-only && cmake --build --preset tests-only
# 该 buildPreset 默认 target 为 check(编译 EXCLUDE_FROM_ALL 测试并跑 ctest)
Preset 用途
tests-only / tests-only-werror 单测;buildPreset 默认 target 为 check
tests-only-tsan / tests-only-asan sanitizer 门禁(见 §16)
full / full-werror gpu 全量
full-cpu cpu 全量(无 CUDA/TRT)

7.3 打包 tarball(自己出 release)

./scripts/make_release_tarball.sh cpu 0.1.0 build    # 产出 dist/*.tar.gz + .sha256
# 平铺打包(根上是 install.sh / opt/mortred / deploy);解到空目录再装

8. Profile 体系详解

一个开关,四层贯穿——profile 不是两套产品,而是同一产品的两种资源档位:

层 开关 cpu 生效方式 gpu 生效方式
构建 MORTRED_BUILD_PROFILE TRT 源码编译排除;factory 对 type="tensorrt" 返回明确错误 全量编译
依赖 install_deps.sh --cpu MNN 以 MNN_CUDA=OFF 构建;ORT 用 cpu tarball;跳过 NVIDIA deb + CUDA/TRT/cuDNN
目录 server TOML profile 字段 + 运行时 MORTRED_PROFILE 只加载 profile="cpu" 与 "any" 条目;缺省字段按 gpu,故 cpu 目录永远显式精选 加载全部
权重 fetch_weights.py --profile 只拉 profiles=["cpu","gpu"] 标记的精选文件 全量 manifest

8.1 运行时切换

export MORTRED_PROFILE=cpu     # supervisor 与 gateway 都读它;缺省 = gpu

过滤发生在 catalog 加载期、去重检查之前——因此 cpu/gpu 变体可安全复用同一端口(同一时刻只有一套在目录里)。

8.2 扩充 cpu 精选集(CHANGELOG 级变更)

  1. conf/server/.../ 增加 profile="cpu" 的 server 配置,指向同一份模型 toml(不要再加 *_cpu_config.toml);
  2. 该 toml 必须是 mnn/onnx——type=tensorrt 且 device=cpu 视为配置错误;
  3. CPU 机器上在这一份文件里把 device 改成 "cpu"(仓库默认是 "gpu");
  4. scripts/gen_weights_manifest.py 的 CPU_WEIGHTS 集合加入该权重路径;
  5. 跑 gen_weights_manifest.py 重新生成 manifest;
  6. 补充该模型的 cpu 冒烟验证,CHANGELOG 记录。

TensorRT 产品没有可指向的共享 mnn toml——cpu 上改用机器 pack 的 model_config 覆盖来服务它(见 §10.7)。

精选集刻意随版本冻结:扩充是发布决策(要为它背性能与验收),不是随手改配置。


9. 权重管理

9.1 机制

Hugging Face 是 ONNX 交换格式仓库(MaybeShewill-CV/mortred_model_server)。 产品 conf 保持 type=mnn 或 type=tensorrt,例外是本来就是 ONNX 的 id (diffusion、MSOCRNET、SAM decoder)以及切到 YuNet 2026may 之后的 LIBFACE。 GPU engine 必须在本机生成(mortredctl prepare / convert_trt_engines.sh)。 CPU 在本地 convert_mnn 就绪前仍用 MNN 作运行时,不要为了「HF 上是 ONNX」去改产品 toml。

每个模型的源路径、IO 合同、许可证见 conf/onnx_sources.json;字节与 sha256 见 conf/weights_manifest.json。双文件导出规则和缺口清单: onnx-interchange.zh-cn.md。

  • 清单:conf/weights_manifest.json——每个文件带 path / size / sha256 / hf_path / profiles / kind;
  • 下载:HF 仓库,断点续传,已存在且 sha256 匹配则跳过;
  • 产品互换文件用 python3 scripts/fetch_weights.py --kind onnx(39 个 static_bs1 图)。.engine 默认不下载,要显式 --kind engine;engine 在本机 prepare 生成。
  • 校验:--check 只验不拉。

9.2 常用命令

python3 scripts/fetch_weights.py --kind onnx       # 39 个产品 static_bs1 ONNX
python3 scripts/fetch_weights.py --profile cpu     # 拉精选运行时子集(约 1 GB)
python3 scripts/fetch_weights.py --profile gpu     # 运行时权重;不拉 .engine
python3 scripts/fetch_weights.py --kind engine     # 显式拉 engine,不是开箱路径
python3 scripts/fetch_weights.py --only yolov8     # 只拉路径含 yolov8 的
python3 scripts/fetch_weights.py --check           # 校验本地完整性
python3 scripts/fetch_weights.py --dry-run         # 只打印将下载什么

9.3 磁盘规划

Profile 首次下载 建议预留
cpu ~1 GB 5 GB
gpu 数十 GB(视模型取舍) 60 GB+

只装部分 GPU 模型?用 --only <关键词> 分批拉,配合 verify_deployment.sh --full 确认。

9.4 离线环境

在有网机器上拉好 weights/ 目录 → 打包拷贝到目标机 → fetch_weights.py --check 校验。 依赖侧同理:install_deps.sh --offline DIR。


10. 机器 Pack 与 TensorRT

Compose 和容器入口会设 MORTRED_AUTOSTART=true 以及 MORTRED_PACK(默认 conf/packs/demo.toml)。这只会拉起 pack 里列出的 catalog id,不是 conf/server/ 整树。仍然是一模型一进程;git 里 conf/server 的 worker_nums 保持 1。Pack 的 worker_nums / model_config 通过环境变量覆盖子进程。

GPU pack 链路(copy → prepare → calibrate --write-pack → doctor → start):

cp conf/packs/gpu_yolo_metric3d.toml /tmp/machine-pack.toml
mortredctl prepare --pack /tmp/machine-pack.toml
mortredctl calibrate --pack /tmp/machine-pack.toml --write-pack
export MORTRED_PACK=/tmp/machine-pack.toml
mortredctl doctor
# 随后用该 MORTRED_PACK 启动 supervisor / compose

仓库里的 TensorRT pack 带 seed 占用样本(gpu_mem_source="seed")。 逐 id 文件 conf/packs/<id>.toml 覆盖全部 39 个产品 id;yolov8.toml、 metric3d.toml、gpu_yolo_metric3d.toml 仍是短的多 id 示例。 Seed MiB 只是可复用的起始样本, 换机 必须 用 calibrate --write-pack 覆盖。不要把校准后的 worker_nums、 非 seed 的 gpu_mem_mib,或 [pack] 的 gpu_name / gpu_memory_total_mib 提交进仓库。demo pack 是带 seed 的 TensorRT MobilenetV2 产品:像任何 TRT pack 一样每台机器要重校准;在纯 CPU 机器上它不拉起任何模型(见 §10.7)。 --write-pack 拒绝直接写 conf/packs/,必须先拷贝。

每个产品 id 一份 seed pack

conf/product_static_bs1.json 里每个 static batch=1 TensorRT engine 对应一个 catalog id(39 个)。每个 id 有一份生成的 seed pack:conf/packs/<id>.toml, 里面只有一节 [pack.<id>],字段为 worker_nums = 1、gpu_mem_mib = 1800、 gpu_mem_at_workers = 1、gpu_mem_source = "seed",以及该 id 的 model_config。产品表变更后重新生成:

python3 scripts/product_static_bs1.py --emit-packs conf/packs
python3 scripts/product_static_bs1.py --check

--check 会在这些情况下失败:某个 id 没有 pack、文件与生成器不一致、节里的 id 与文件名不一致,或 conf/packs/*.toml 既不是产品 id 的 <id>.toml,也不是 遗留的多 id 示例 demo.toml、yolov8.toml、metric3d.toml、 gpu_yolo_metric3d.toml。这四份仍是短示例。产品覆盖以逐 id 文件为准。

mortredctl prepare、scripts/prepare_pack.sh、scripts/pack_trt.py 和 MORTRED_PACK 都只接受一份 pack。要同时拉起多个 id,先把对应的逐 id 文件合并成机器本地 pack,后续命令都用这一份路径。scripts/compose_packs.py 按参数顺序抄写每一节 [pack.<id>];多份文件里的 [pack] 表在赋值文本相同 时合并,id 重复则拒绝。它也拒绝写入 conf/packs/ 与 conf/server/。

python3 scripts/compose_packs.py \
  conf/packs/yolov8s.toml conf/packs/libface.toml \
  -o /tmp/yolov8s-libface.toml
mortredctl prepare --pack /tmp/yolov8s-libface.toml
mortredctl calibrate --pack /tmp/yolov8s-libface.toml --write-pack
export MORTRED_PACK=/tmp/yolov8s-libface.toml
mortredctl doctor

单个 id 用同一条链路:cp conf/packs/<id>.toml /tmp/<id>.toml(或只 compose 这一个文件)。prepare 只转换 pack 里列出的 id。一份单 id pack 不会构建其余 engine。是否同时驻留全部 id 取决于本机显存;compose 需要同时拉起的那个子集即可。

10.1 Pack 文件

# conf/packs/demo.toml — 仓库示例:带 seed 的 TRT MobilenetV2
# (git 里保持 worker_nums=1 与 seed stamp;每台机器都要重校准)
[pack.mobilenetv2]
worker_nums = 1
model_config = "conf/model/classification/mobilenetv2/mobilenetv2.toml"
gpu_mem_mib = 1800
gpu_mem_at_workers = 1
gpu_mem_source = "seed"

# calibrate --write-pack 之后的机器本地副本,例如 /tmp/machine-pack.toml
[pack.yolov8s]
worker_nums = 4
gpu_mem_mib = 2100
gpu_mem_at_workers = 4
gpu_mem_source = "nvml_pid"
# model_config = "conf/model/object_detection/yolov8/yolov8s.toml"  # 可选变体

未知 id 会让 supervisor 启动失败。把 MORTRED_PACK 指到机器上的 pack(compose 环境变量、systemd supervisor.env、或进程环境)。

10.2 为本 pack 准备 TensorRT engine(GPU)

Engine 绑定 这张卡 + 这套 TensorRT。只转 pack 用到的(上链第 2 步):

mortredctl prepare --pack /tmp/machine-pack.toml       # 或 scripts/prepare_pack.sh

supervisor 拒绝 spawn engine 文件缺失或为空的 TensorRT id(状态 failed, 不 crash-loop)。/ready 才是真正能加载,不只是文件非空。prepare 之后 TensorRT id 仍需要本机占用 stamp(§10.3)才能 spawn——git 里的 seed 不够。

每个 TensorRT pack——包括 demo 的 MobilenetV2——都必须在目标 GPU 上 prepare。MORTRED_AUTO_BUILD_ENGINES=true 仍会转 整个 zoo,且 默认关闭。

10.3 校准 worker_nums 与占用 stamp

先停 supervisor / 残留的 mortred-model-server。脚本会自己在目录端口上起服 (YOLOV8 = 9056);端口占用是 start_failed,不是 OOM。--write-pack 前先把 仓库示例 pack 拷到机器本地文件(第 1 步),再校准(第 3 步):

ss -ltnp | grep 9056 || true
python3 scripts/calibrate_pack.py --pack /tmp/machine-pack.toml \
    --output logs/calibrate-yolov8.json --write-pack

默认时长 30 秒。更短的 --duration 会使 release_measurement=false, --write-pack 不写回。不传 --workers 时按 id 选网格:docs/benchmark-h3/<id>.txt 的 Throughput ≥ 500 qps 扫 1,2,4,8,16,其余扫 1,2,4,8。[pack.<id>] worker_sweep 覆盖该网格,--workers 再覆盖二者。每一档的并发等于该档 worker 数。某一档起不来、 OOM、非 429 错误率或 429 比例达到 1%、p99 超过 [pack.<id>] p99_ms_cap(或内置 seed 上限)、读不到排队与推理直方图、排队 p50 大于推理 p50,或 RPS 涨幅不足 10%, 扫描就停。conf/product_static_bs1.json 里的每个 id 都在 scripts/calibrate_pack.py 的 P99_CAP_MS 中有 seed。既没有 seed 也没有 [pack.<id>] p99_ms_cap 时,校准在扫描前退出并打印缺 cap 表,不会跳过延迟门。 worker_nums=1 自己没过门的 id 保持 eligible=false,不写入。两个及以上 合格 id 共驻时只量显存。预算是 [pack] gpu_memory_budget_mib,否则为卡容量的 85%。 超预算或 OOM 时,把显存最大的 id 沿网格降到已经过门的低一档;端口占用不降档。 --write-pack 写入的是这个 final_worker_nums。

JSON 里 gpu_mem_mib_* 是进程占用(nvml_pid / nvml_name),WSL 上往往是 spawn 前的 整卡 delta,不是整卡 memory.used。--write-pack 会在机器副本 上覆盖 seed(或旧 stamp),写入 gpu_mem_mib、gpu_mem_source、 gpu_mem_at_workers,以及 [pack] 的 gpu_name / gpu_memory_total_mib。

supervisor 拒绝 spawn 已启用 pack 里没有 gpu_mem_mib stamp、或 worker_nums 与 gpu_mem_at_workers 不一致的 TensorRT id(状态 failed, 不 crash-loop)。错误字符串就是下一命令: mortredctl calibrate --pack <pack> --write-pack。有总量字段时,联合预算是 已运行兄弟 stamp 之和 + 本项,对比 gpu_memory_total_mib × (1 − gpu_reserve_pct/100)。 occupancy_policy=off 或 MORTRED_OCCUPANCY_ENFORCE=0 会跳过 spawn 闸(与 MORTRED_EXPOSE=unsafe 同类);mortredctl doctor --strict 仍失败。非法 occupancy_policy 会让 pack 无法 apply。

10.3.1 Doctor 再 start

校准后跑 doctor(第 4 步),再用同一 pack 启动(第 5 步):

export MORTRED_PACK=/tmp/machine-pack.toml
mortredctl doctor          # 或:mortredctl doctor --strict
# 启动 supervisor / compose,让 MORTRED_PACK 注入 worker_nums

10.4 全量 zoo 转换(可选)

./scripts/convert_trt_engines.sh --list
./scripts/convert_trt_engines.sh
./scripts/convert_trt_engines.sh --force

需要 trtexec(sudo ./scripts/install_deps.sh --nvidia → 3rd_party/bin/)。

10.5 容器首启自动转换(可选)

docker compose --profile gpu up -d -e MORTRED_AUTO_BUILD_ENGINES=true

发生在 supervisor autostart 之前,耗时分钟级,默认关闭。日常用 §10.2。 mortredctl doctor 对缺失 pack engine 或占用 stamp 告警;--strict 失败。

10.6 ONNX Runtime CUDA arena 与 pack 占用

ORT CUDA 以前是 gpu_mem_limit = 0(arena 无限涨)。现在默认 每个 session 2048 MiB([MODEL.backend] gpu_mem_limit_mb,或 MORTRED_ORT_GPU_MEM_LIMIT_MB)。worker_nums=4 最多四份 arena。0 恢复不限制。 MNN / TensorRT 没有对等旋钮。进程占用由 supervisor spawn 按 pack stamp 卡住 (§10.3),不是把 gpu_mem_limit_mb 接到 TRT/MNN session。

10.7 纯 CPU 运行(不提供默认 cpu 产品)

仓库刻意不随包发布默认 cpu 产品配置:conf/model/** 下没有 *_cpu.toml,conf/server/** 下没有 *_cpu_config.toml(一致性门禁会在 它们重新出现时报错)。CPU 用法按上面的构件在每台机器上自行组装:

  1. 构建 / 安装 cpu 档——install_deps.sh --cpu、CMake preset full-cpu,或 docker compose --profile cpu build。

  2. 为想要的模型写一份 cpu 模型 toml——type = "mnn" 或 type = "onnx" 且 device = "cpu"。权威样例是 conf/ci/mobilenetv2_hosted.toml (由 hosted golden 门禁保持真实)。type="tensorrt" 的 toml 在 cpu 上 按设计 fail-closed 报错。

  3. 写机器 pack 选中 catalog id,并把 model_config 指向你的 cpu toml (cpu 后端不需要占用 stamp):

    # /tmp/cpu-pack.toml
    [pack.mobilenetv2]
    worker_nums = 1
    model_config = "/tmp/mobilenetv2_cpu.toml"
  4. 拉取 cpu 标记的权重(conf/weights_manifest.json 里 profiles=["cpu","gpu"] 的条目): python3 scripts/fetch_weights.py --profile cpu。

  5. 以 cpu profile 启动——export MORTRED_PROFILE=cpu 与 export MORTRED_PACK=/tmp/cpu-pack.toml,再启动 supervisor (compose 挂载该 pack,或 tarball / systemd 入口)。

仓库自带的 conf/packs/demo.toml 是带 seed 的 TensorRT 产品:它的 MobilenetV2 id 不在 MORTRED_PROFILE=cpu 目录里,所以用默认 pack 做 cpu compose 启动只会拉起 supervisor + gateway。这是"不提供默认 cpu 产品" 的预期行为,不是故障。

10.8 HTTP RPS 证据(PF1 / TP1)

合格 HTTP RPS 一次只跑一个 id:prepare → calibrate(--raw)→ 只部署该 pack → scripts/server/http_infer_rps.py --raw,并发为 worker_nums×2,时长 至少 30 秒。驱动记录的是 60 秒。

覆盖集九个 id(运维 / TP2 子集)在 docs/perf/coverage_set_http_rps.md。产品目录 全量是 conf/product_static_bs1.json 的 39 个 id,清单在 docs/perf/product_static_http_rps.md。 该文件在 GPU 主机填入之前是骨架,不编造 RPS。任一 id 仍是 blocked / evidence=missing 时 --check 非 0。--check-skeleton 只核对标题,并要求 缺失行明确写成非 PASS。

python3 scripts/coverage_http_report.py --check
python3 scripts/coverage_http_report.py --catalog product_static --check-skeleton
python3 scripts/coverage_http_report.py --catalog product_static --check
python3 scripts/coverage_http_report.py --catalog product_static --id yolov8n

prepare 用 mortredctl prepare --id <id>。若 conf/packs/<id>.toml 存在且 只含该 id,驱动把该种子(含 model_config)复制到临时 pack,因为 calibrate --write-pack 拒绝写 conf/packs/。否则在 /tmp 写单 id 的 [pack.<id>] worker_nums = 1。不改 git 里的 pack,也不要求其它 id 同时驻留。 PASS 行经 pf5_retest.record_baseline 写入 docs/perf/baselines/<id>.json (同一硬件只创建一次)。

11. 认证与安全

11.1 三层 token

token 保护对象 配置位置
MORTRED_API_TOKEN supervisor 管理 API + Web 控制台 /etc/mortred/supervisor.env(tarball)/ 容器环境变量
MORTRED_GATEWAY_AUTH_TOKEN 网关推理入口 同上
MORTRED_METRICS_TOKEN 网关 GET /metrics scrape Bearer 必填(含环回);不能与推理/管理 token 相同
openssl rand -hex 24    # 生成方式(每个 token 各用一次)

fail-closed 语义:监听地址非 127.0.0.1 且未配 token → 进程拒绝启动并打印原因。 非环回网关在 MORTRED_METRICS_TOKEN 为空或与推理/管理 token 相同时同样拒绝启动。 永远不要带洞上线。该门闩不终结 TLS(用反代)。进程启动不因 token 过短而失败; mortredctl doctor --strict 会把短 token / 明文监听等警告变成失败。默认 doctor 仍只警告。 不要把推理 token 当作 scrape 密钥。

11.2 多租户 API Key(网关层)

单 token 之外,网关支持按 key 管理(哈希存储、scope、限流)。没有热加载:改文件后重启 gateway 子进程。 用 ./scripts/mint_api_key.sh --name client-a --scope inference --append conf/api_keys.toml 生成密钥(32 字节随机;明文只打印一次)。不要哈希人类可记的口令:校验用的是无盐 SHA-256,配置一旦泄漏,弱口令可被离线还原;mortredctl doctor 会拒绝文档里那个示例弱口令的哈希。

# conf/api_keys.toml
[keys.client-a]
hash = "sha256(<32 字节随机密钥的 sha256>)"   # 用 scripts/mint_api_key.sh 生成
scope = "inference"
rate_limit_qps = 100
enabled = true
curl -X POST -H "Authorization: Bearer $MORTRED_API_TOKEN" \
     http://localhost:8787/api/v1/servers/__gateway/restart

详见 api-keys.md(含密钥轮换零停机流程)。

11.2.1 网关限流顺序(ST2)

模型路由的门闩顺序:

OPTIONS → /healthz → /metrics → 路由 → 可选的鉴权前滥用限制 → 鉴权 → 已鉴权来源 GCRA → 按 key 的队列 / 在途 → 转发。

[gateway.rate_limit] 是已鉴权推理配额(响应层 gateway-gcra),只在 Bearer 鉴权成功之后扣减。缺 token 或 token 无效是 401,不会准入该桶,因此未鉴权突发不会减少同一地址上后续合法客户端的剩余配额。网关不会先准入再在 401 时归还。

[gateway.abuse_limit] 可选,缺省关闭。它是独立的鉴权前滥用限制(层 gateway-abuse),与已鉴权推理桶不共享。MORTRED_RATE_LIMIT=off|shadow|enforce 只作用于 [gateway.rate_limit]。

按 key 的 rate_limit_qps(层 api-key-gcra)只在该 key 已被识别且允许推理之后扣减。未知、禁用或非推理 scope 的 key 不占用 per-key 窗口。

11.3 上线前安全检查单

  • 两个 token 均为 ≥ 32 字符随机值,且互不相同
  • /etc/mortred/supervisor.env 权限 600、属主 mortred
  • 8080/8787 发布在 127.0.0.1,除非前面已有 TLS 反代
  • 即使有 TLS,管理面 8787 也不要直接暴露到公网
  • conf/api_keys.toml(若使用)600 权限,不入库不入镜像
  • 反代上启用 TLS(Mortred 自身是 HTTP);见 §11.4
  • 防火墙放行清单里没有模型进程端口(它们本就只绑 loopback)
  • Grafana / Prometheus 端口留在环回;Grafana 密码不是镜像默认值
  • Prometheus 不要去刮已经映射出环回的模型端口
  • 若 8080 能从环回以外访问,已设置独立的 MORTRED_METRICS_TOKEN

11.4 TLS 反代(Nginx)

Mortred 不终结 TLS。一等公民是 主机网络上的 Nginx(mortredctl init-edge)。 不要在 gateway / supervisor 进程里做 TLS。不要把 Nginx 放进 Docker bridge 再去打宿主机的 127.0.0.1:8080。

mortredctl init-trust
set -a && . conf/local/trust.env && set +a
mortredctl init-edge --mode lan --server-name localhost
nginx -t -p conf/local/edge -c nginx.conf
# 局域网:浏览器信任一次 conf/local/edge/tls/ca.pem
# 公网域名:mortredctl init-edge --mode acme --server-name infer.example.com
#   然后 certbot certonly --webroot -w /var/www/mortred-acme -d infer.example.com
#   (不要用 certbot --nginx,它会改写站点文件)

8080/8787 留在 127.0.0.1。compose profile: edge 仅 Linux network_mode: host。

mortredctl doctor 会在有效监听非环回、token 短于 32 字符、token 相同、或未设 MORTRED_METRICS_TOKEN 时打印警告。--strict 把警告变成失败。doctor 不实现 TLS。


12. 升级与回滚

12.1 mortredctl upgrade(原地,推荐)

mortredctl upgrade              # 升到最新 release,保持当前 profile
mortredctl upgrade v0.2.0       # 指定版本

流程:下载该 profile 的 tarball → sha256 校验 → 备份 conf/ 到 conf.backup-<时间戳> → 覆盖安装 /opt/mortred(权重不动)→ 重启服务 → 自动跑 doctor。

12.2 回滚

cd /opt/mortred
sudo cp -a conf.backup-<时间戳> conf          # 恢复配置
# 重装旧版本 tarball(或 docker 换回旧 tag),然后:
mortredctl doctor

12.3 版本策略

  • 原地升级仅支持相邻 minor 版本;
  • 跨更大版本:导出配置 → 全新安装目标版本 → 手工迁移(scripts/migrate_model_config.py 辅助);
  • 配置兼容性破坏逐版本记录于 CHANGELOG.md。

13. 监控集成

开箱即用的 Prometheus 指标端点:

端点 内容
GET :8080/metrics 网关:HTTP 请求计数/时延、推理时延、队列等待、worker 可用性(一律要 scrape token)
GET :8787/api/v1/metrics supervisor:进程状态、重启计数(需要 Bearer MORTRED_API_TOKEN)

仓库自带一套本机监控栈(Prometheus + Grafana + 告警规则)。端口只绑环回; 启动前必须设置 Grafana 密码。默认只刮网关 /metrics,详见 monitoring-guide.zh-cn.md。

该 compose 里的 Prometheus 以 uid 65534 运行。scrape 密钥文件必须是 mode 600 且属主为该 uid(裸机则属主为用户 prometheus)。 write_prometheus_credentials.sh 会做 chown。在 Prometheus 容器内抓取 要用 host.docker.internal:8080,不要用 localhost:8080(那是 Prometheus 自己)。Linux compose 通过 extra_hosts 解析该名字。

set -a && . conf/local/trust.env && set +a   # MORTRED_METRICS_TOKEN
./scripts/write_prometheus_credentials.sh    # 可能 sudo 把属主改成 65534:65534
export GRAFANA_ADMIN_PASSWORD="$(openssl rand -hex 16)"
docker compose -f deploy/docker-compose.monitoring.yml up -d
# Grafana: http://localhost:3000
# 告警规则: deploy/alert-rules.yml(含过载拒绝率告警)
# 裸机:sudo cp deploy/prometheus.yml /etc/prometheus/ &&
#   sudo ./scripts/write_prometheus_credentials.sh /etc/prometheus/mortred_metrics_token

14. 故障排查

先跑这一条,八成问题直接定位:

mortredctl doctor          # 或 verify_deployment.sh --live

14.1 症状速查表

症状 最可能原因 修复
服务起不来,日志见 refuse to start 非环回监听但缺 token 配好两个 token 再启动(§11.1)
401 且带 WWW-Authenticate token 错/缺 核对 Authorization: Bearer ... 与对应 token
catalog 是空的 MORTRED_PROFILE / pack 不匹配,或 cpu + demo TRT 种子 确认环境变量与 pack;cpu 需自建 pack(§10.7),不要求随包 *_cpu 产品
模型进程反复重启 权重缺失 / engine 缺失 / 配置错 mortredctl status、mortredctl logs <id>;TRT 先 mortredctl prepare
calibrate Cannot start server / 端口占用 supervisor 或残留模型仍在听 先停 systemd/compose/supervisor,再 ss -ltnp 看目录端口(§10.3)
下载权重 404/超时 HF 不可达 离线流程(§9.4)或配置镜像
sha256 校验失败 下载损坏 / manifest 过期 删除该文件重拉;仍失败则重新生成 manifest
429 响应 队列满或 key 限流 调 max_queue_depth / rate_limit_qps;看 /metrics
gpu 模型 init 报 "tensorrt backend is not compiled" cpu 构建跑了 trt 配置 换 gpu 构建/镜像,或该模型用 cpu 配置
容器里 engine 全缺 pack 未 prepare,自动转换默认关 §10.2;全量 zoo 转换是可选项

14.2 日志位置

轨道 命令
Docker docker compose --profile <p> logs -f
systemd journalctl -u mortred-supervisor -f
单个模型进程 mortredctl logs <server-id> --limit 200

14.3 深挖

supervisor 为什么把模型进程绑在 loopback?

安全边界设计:外部只能经网关(鉴权/限流/审计的单一咽喉)到达模型;supervisor 与模型间用 internal token 二次确认。即使 9xxx 端口误暴露,没有 internal token 也无法推理。

升级后某个模型起不来了怎么办?
  1. mortredctl logs <id> 看模型进程报错;
  2. 对比 conf.backup-<时间戳> 里该模型的配置差异;
  3. 查 CHANGELOG 该版本是否有配置不兼容说明;
  4. 仍无解 → 用备份配置 + 旧版本 tarball 回滚(§12.2)。

15. FAQ

怎样验收长任务 POST /jobs 是立刻 202?

按 async-jobs-customer-test.zh-cn.md 逐步做。 POST /v1/models/{id}/jobs 必须在毫秒级返回,同一 payload 的 /infer 仍要跑完采样时间。 提交后立刻 /result 必须是 409。

cpu profile 以后会支持更多模型吗?

会按版本扩充(走 §8.2 的正式流程),但保持"精选"定位——每个入选模型都要背 CPU 性能与验收。 扩充记录见 CHANGELOG。

能同时跑 cpu 和 gpu 两套吗?

一台机器一个运行时 profile(MORTRED_PROFILE)。要同时服务两类负载,部署两套实例并区分端口。

Docker 轨道和 Tarball 轨道功能有差别吗?

没有。同一二进制、同一配置、同一验收脚本。选轨道只看运维习惯。

权重必须放 /opt/mortred/weights 吗?

模型配置里的路径是相对安装树解析的。tarball 轨道默认在 /opt/mortred/weights; Docker 轨道挂载到容器内该路径即可,宿主机位置随意。

worker_nums 写在哪?

git 里 conf/server 保持 worker_nums=1。并发写在机器 pack 的 [pack.<ID>] worker_nums,由 MORTRED_PACK 注入。用 mortredctl calibrate 扫曲线,--write-pack 写到机器本地 pack,然后重启 supervisor(§10.3)。

没有 python3 怎么拉权重?

任意机器拉好后整体拷贝 weights/ 目录过去,目标机 fetch_weights.py --check 校验(校验也需要 python3;完全无 python 的环境请用有 python 的机器完成校验后再拷贝)。


16. 验收门禁汇总

门禁 命令 覆盖
静态 ./scripts/verify_deployment.sh --basic 脚本语法 / manifest / compose YAML / 依赖清单 / security_warn.sh --self-test
完整 ./scripts/verify_deployment.sh --full + 本地权重 sha256 + 3rd_party 完整性
实时 ./scripts/verify_deployment.sh --live + 网关探活(healthz 公开 + 推理需鉴权)
一键 mortredctl doctor live 封装 + 安全警告(默认不失败;--strict 会失败)

CI 侧:每次变更在无 GPU runner 上跑 cpu-profile 全量构建 + 全部单测——条件编译路径不会悄然腐烂; sanitizers job 持续运行 TSAN/ASan 门禁。


发现文档与实际行为不符?那是 bug,欢迎提 issue。