本页是 how_to_add_new_model.md 的中文版, 讲解统一后端层的结构与生命周期。按任务一步步操作的中文指南见 model-developer-guide.md(英文,路径导向)。
当前实现基于
BackendCvModel。旧版文档描述的 “自定义输入 → 模型内部输入 → session → 模型内部输出 → 自定义输出” 五层转换已经不存在,本页已按现行 API 重写。
# 查看支持的任务
python scripts/new_model.py --list-tasks
# 预览将要生成的文件,不落盘
python scripts/new_model.py --task classification \
--name efficientnet --class EfficientNet \
--backend mnn --dry-run
# 实际生成
python scripts/new_model.py --task classification \
--name efficientnet --class EfficientNet \
--backend mnn
会生成 5 个文件:
| 文件 | 需要你填的内容 |
|---|---|
src/models/classification/<file>.h |
类骨架,已继承 BackendCvModel |
src/models/classification/<file>.inl |
preprocess / postprocess / on_init 三个钩子 |
conf/model/classification/<name>/<name>_config.toml |
[SECTION] + .backend + .params |
test/<file>_output_contract_unittest.cc |
输出契约测试 |
docs/models/classification/<name>.md |
文档骨架 |
此时模型可以直接编译,所有钩子返回 MODEL_NOT_IMPLEMENTED,
半成品不会被误当成能跑的模型启动。
src/models/object_detection/rtdetr_detector.* 是一个已入库的生成样例,
同时充当模板可编译性的哨兵。
脚手架还会打印两段它刻意不自动应用的片段:catalog 条目和测试目标注册。
IO 类型在 src/models/io/ 下,每个任务一个头文件。
common_input.h 存放共享输入(mat_input / file_input / base64_input /
pair_mat_input),各任务头文件存放自己的 std_*_output。
只 include 你需要的那个任务头。旧的 model_io_define.h 仍然可用, 但它是一个会把所有任务都拉进来的兼容聚合头。
可加载的图像输入走默认的 prepare_inputs 路径;
任务默认输出(std_*_output)是推荐选择。
先读这几个参考实现:
prepare_inputs)template <typename INPUT, typename OUTPUT>
class MyModel : public jinq::models::BackendCvModel<INPUT, OUTPUT> {
public:
MyModel() : jinq::models::BackendCvModel<INPUT, OUTPUT>("MY_MODEL") {}
private:
// 图像 -> 命名输入张量(图像模型必须实现)
std::vector<jinq::models::backend::NamedTensor> preprocess(const cv::Mat &image) override;
// 命名输出张量 + 请求几何 -> 任务输出
jinq::common::StatusCode postprocess(const std::vector<jinq::models::backend::NamedTensor> &outputs,
const jinq::models::backend::InferenceContext &context,
OUTPUT &output) override;
// 可选:读取 [MY_MODEL.params] 中的模型专属键
jinq::common::StatusCode on_init(const toml::table ¶ms) override;
};
要点:
"MY_MODEL")。ImagePipeline:resize / 裁剪 / 颜色转换 / 归一化,一次调用打包成 NCHW 或 NHWCOutputReader:替代手搭的输出契约SessionIoValidator:替代手写的 session shape 检查ParamReader:带范围诊断的 TOML 参数读取src/models/ 下每个已迁移的模型都是例子。
session().inputs() / session().outputs() 暴露
TensorInfo{name, dtype, shape, dynamic};从它推导网络输入尺寸,不要写死。backend::Tensor::make<float>(shape) 产生张量;shape 必须具体,
并与模型文件期望的布局一致(MNN TENSORFLOW 导出配 input_layout = "nhwc",
TensorRT / ONNX 是 nchw)。name 取张量(见
tensor_contract.h 的 find_output)。prepare_inputs,
不重写 preprocess。InferenceContext 里;绝不存成模型成员,
因为一个 worker 可能正在处理动态 batch。context.source_size。
产生坐标的模型用共享的 request-geometry helper,不要除以输入尺寸成员。MODEL_OUTPUT_CONTRACT_FAILED,
而不是产出一个解了一半的任务结果。<key>_backend 子表,
用 make_session("<key>_backend") 创建额外 session,
并在 run_sessions 重写里编排它们。sessions() 声明,而不是手写 create / validate / reset 序列。
它刻意不做运行编排。[MY_MODEL]
[MY_MODEL.backend]
type = "mnn" # mnn | onnx | tensorrt
model_file_path = "../weights/my_model/model.mnn"
device = "gpu" # cpu | gpu
threads = 4
gpu_mem_limit_mb = 2048 # 仅 onnx+cuda;0 = 不限制;默认 2048
input_layout = "nhwc" # 仅 mnn: auto | nhwc | nchw
[MY_MODEL.params]
score_threshold = 0.25
完整键参考见 about_model_configuration.md。
旧的 BACKEND_DICT / XXX_TRT / XXX_ONNX / XXX_MNN 三段式配置已经移除;
用 scripts/migrate_model_config.py 迁移
(先 --dry-run,CI 里用 --check)。
每个任务在 src/factory/<task>_task.h 里有一个显式 catalog。
新增一个被服务的模型现在是一行加一个 creator——
没有手写的 server 注册 lambda,也没有复制的 CvServerSpec 块:
// src/factory/my_task.h
template <typename INPUT, typename OUTPUT>
std::unique_ptr<BaseAiModel<INPUT, OUTPUT>> create_my_model(const std::string &name) {
(void)name;
return std::make_unique<MyModel<INPUT, OUTPUT>>();
}
using Output = jinq::models::io_define::my_task::std_my_task_output;
using Entry = jinq::factory::cv_catalog::CvModelEntry<Output>;
inline const std::vector<Entry> &catalog() {
static const std::vector<Entry> entries = {
Entry{"MY_MODEL", "My model display name", "MY_MODEL_SERVER",
&create_my_model<jinq::server::Base64Input, Output>,
&jinq::server::response::fill_my_task},
};
return entries;
}
剩下的交给 factory::cv_catalog::create_server(catalog(), "MY_MODEL", server_name):
它在 ServerFactory<BaseAiServer> 里注册 creator,
并构建通用的 CvModelServer<Output>。
刻意存在两种形态:
CvModelEntry<OUTPUT> 携带 worker creator 和
response filler)一个任务有多个输出契约时,按契约拆成多个 typed catalog,
不要合并成一个类型擦除的列表——
见 obj_detection_task.h 里的
catalog() 与 face_catalog()。
test/model_catalog_unittest.cc 会在 catalog 行引用了不存在的 TOML section
或 model_config_file_path,或 model / server section 跨任务重复时报错。
cmake --preset full && cmake --build --preset full
scripts/run_tests.sh build/full -R model_golden_test --output-on-failure
用 model_golden_registry.h 里的一个宏 注册 golden 用例(完整列表和两命令基线流程见 开发者指南)。 用 golden_drift_check.py 证明重构没有改变任何数值, 用 POSTPROCESS_CONTRACT_TEST 覆盖七项拒绝矩阵。
容差按任务定:检测用 score / box-IoU,稠密输出用指纹 diff。