mortred_model_server

Model Contract Governance

This page records the contract rules applied across the model layer. It is the review checklist for new models and for changes to existing model families.

Request geometry

InferenceContext is request-scoped and carries two sizes:

Field Meaning
source_size Size of the image supplied by the user
network_size Concrete tensor size used for this inference

Spatial models must not derive either size from mutable model members in postprocess. Use request_geometry.h:

This policy is applied to object detection, scene segmentation, OCR, matting, enhancement, SuperPoint, DepthAnything, Metric3D and FastSAM. Multi-session models which currently process requests synchronously were audited with the same rule; their request geometry remains local to one run_sessions call. Latent-space diffusion models and CLIP encoders have no source-image geometry, but their f32 outputs use the same output-contract boundary.

Output contracts

Floating-point outputs are validated at the model boundary. The preferred entry point is OutputReader, which wraps the same contract as f32_output.h behind a fluent call:

auto view = jinq::models::backend::OutputReader(outputs, "output")
                .f32()          // dtype
                .shape({1, -1}) // rank + shape, -1 = any
                .finite()       // reject NaN / Inf
                .read();
if (!view.ok()) {
    return view.status;
}

Both paths enforce the same rules:

The rejection matrix for a new model is generated by POSTPROCESS_CONTRACT_TEST - one line buys seven independently filterable tests (missing output, wrong dtype, wrong rank, wrong shape, short buffer, NaN, Inf). While a model is still a scaffold every variant fails with MODEL_NOT_IMPLEMENTED, which the harness accepts as an explicit rejection, so the macro works before the decoder exists.

model_output_contract_unittest covers the shared helper and representative models from classification, segmentation, OCR, matting and enhancement. object_detection_output_contract_unittest covers detector-specific layouts.

Integer argmax outputs use validate_output_tensor directly with the exact DType::I32 / DType::I64 layout; finite-value checks apply to f32 outputs.

Configuration limits

The repository-wide image defaults protect services from oversized decoded inputs:

max_image_pixels = 16777216
max_image_side = 8192

A model may explicitly raise max_image_pixels when its normal user input is a full-size camera image. The override must be documented in that model configuration. MODNet and PPMatting, for example, accept 24 MP portrait photos.

Golden regression

Contract tests reject malformed tensors before task decoding. Golden tests then lock the numerical behavior of valid models:

The local full GPU regression currently executes the complete committed model_golden_test suite. Weight-free environments skip weighted cases by design rather than reporting them as passed.

One blind spot to keep in mind: the drift guard protects the baseline files and the tests protect the tolerance. Neither catches a regression the test input is insensitive to. When you add a golden case, prefer a colour input - a grayscale image cannot detect a channel-order swap because R == G == B there. That exact gap let a dropped BGR-to-RGB conversion pass undetected until the input was switched to a colour image.

Session IO validation

Hand-written walks over session().inputs() / session().outputs() are replaced by SessionIoValidator, which names the offending tensor in the error message instead of printing the whole session:

const auto info = jinq::models::backend::SessionIoValidator(session())
                      .input("input")
                      .f32()
                      .rank(4)
                      .nchw()
                      .channels(3)
                      .static_shape()
                      .validate();
if (!info.ok()) {
    return jinq::common::StatusCode::MODEL_INIT_FAILED;
}

For a fixed set of distinct engines addressed by name, prefer MultiSessionModel: declare the engines through sessions() and the base class owns the create / validate / reset-on-failure sequence. An empty IoSpec name means “create the engine but let the model validate its IO”, for engines whose tensors are optional or backend-dependent. The base class deliberately does not orchestrate the runs.