mortred_model_server

如何新增一个模型服务

本框架中新增一个 server 自注册表化重构后不再需要编写约 200 行的模板类:每个可服务模型是家族 catalog() 里的一条 CvModelEntry(两个 TOML 段名、worker 工厂、响应序列化器)。ProductIndex 把这一行投影到两个统一入口。通用实现位于 jinq::server::CvModelServer<MODEL_OUTPUT>,它构建在编排器 jinq::server::BaseAiServerImpl<WORKER, MODEL_OUTPUT> 之上。鉴权、限流、请求校验、单请求超时、worker 租约(worker_pool.h)、Prometheus 指标与 /openapi.json 仍由该栈提供,只是拆成组合模块而不是一个上帝类。模型输入统一使用 base64 编码图像。下面以新增 densenet 图像分类服务为例;模型本身参考模型开发者指南。

第 1 步:定义输出数据类型 :monkey_face:

与新增模型一致。各视觉任务的默认输出类型定义在 model_io_define.h 中,以 std_*_output 命名。分类任务默认输出:

namespace classification {
    struct cls_output {
        int class_id;
        std::vector<float> scores;
    };
    using std_classification_output = cls_output;
}

class_id 等于 scores 中最大分数的下标。如果你的任务需要新的输出结构,先在此定义——server spec 与响应序列化器都引用它。

第 2 步:在 catalog 加一行

打开模型所属任务的工厂头文件(示例为 src/factory/classification_task.h),向 catalog() 追加一条 CvModelEntry。这一行就是产品身份:model_section 是 --model 键,server_section 对应 TOML 段,make_worker + fill_response 即可 HTTP 服务,家族默认 vis/默认图即可 bench。

inline const std::vector<Entry> &catalog() {
    static const std::vector<Entry> entries = {
        // ...已有模型...
        Entry{"DENSENET", "densenet classification", "DENSENET_CLASSIFICATION_SERVER",
              &create_densenet_classifier<ImageInput, Output>,
              &jinq::server::response::fill_classification,
              classification_param_specs()},
    };
    return entries;
}

ProductIndex 启动时遍历各家族 catalog()。没有第二张产品表,也不再写 create_densenet_cls_server 薄包装。CvModelServer<Output>::init 会读取 server 段 (worker 池、超时、鉴权、限流,见服务配置说明)、 加载 model 段引用的模型配置、创建 worker_nums 个 worker 并装配 HTTP 服务。

如果输出是全新结构,先在 src/server/response_serializers.h 补充对应序列化器并让 fill_response 指向它;字段名与 JSON 类型必须与 docs/openapi.json 的 components.schemas 一致(用 python scripts/gen_openapi.py 重新生成)。

第 3 步:统一入口,不再新建 ELF

不要再添加 src/apps/server/*.cpp 或 add_mortred_app。ProductIndex 会投影家族 catalog 里的每一行 CvModelEntry,因此 catalog 加一行之后即可:

mortred-model-server.out --model DENSENET /path/to/densenet_server_config.toml
mortred-model-benchmark.out --model DENSENET /path/to/densenet_config.toml [image]

然后:

  1. 在 conf/server/<task>/<model>/ 下添加配置,写上 model = "DENSENET" 和 server_exe = "mortred-model-server.out"(复制同类配置并调整 server_uri、port、worker_nums);
  2. python3 scripts/check_consistency.py 必须保持绿色(catalog id ↔ conf model=, conf/ci_hosted_golden.json 的 catalog_tiers:hosted / gpu-smoke / nightly, 以及 hosted 档必须提交 test/golden/<case>.json 或 .png);
  3. 运行 python3 scripts/gen_openapi.py,并提交 docs/openapi.json 与 src/server/openapi_doc.h。生成器读的是 [*_SERVER] 节名 和 server_uri, 因此新增服务或改这两个字段都必须 regen(否则会留下旧的 Request_* schema)。 port / worker_nums 不进入 OpenAPI。若同时改了 catalog 的 server_section 或 ParamSpec,先刷新 docs/contract_dump.json(contract_dump 二进制), 再跑 gen_openapi.py。

第 4 步:框架已经替你做的事

通常不需要触碰请求服务逻辑。BaseAiServerImpl 仍负责 HTTP 前置门与 go-task 生命周期(serve_process / do_work / do_work_cb)。JSON 解析(400/413/415/405)、 Bearer 鉴权、按 IP 限流、worker 租约(计入超时预算)、模型推理、经 fill_response 的序列化、Prometheus 指标与结构化请求日志,实现落在组合头文件里 (http_wire.h、request_admission.h、worker_pool.h、item_exec.h、 async_endpoints.h)。