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.
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:
compute_letterbox_geometry / unmap_letterbox_bbox with
ImagePipeline::letterbox (Ultralytics keep-ratio, pad 114). Do not stretch
those weights with GeometryScale.make_geometry_scale then scale_bbox / scale_point.validated_source_size before resizing to the source image.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.
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:
MODEL_EMPTY_OUTPUTMODEL_OUTPUT_CONTRACT_FAILEDMODEL_OUTPUT_CONTRACT_FAILEDThe 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.
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.
Contract tests reject malformed tensors before task decoding. Golden tests then lock the numerical behavior of valid models:
model_golden_registry.h instead of
copying the seven-step flow;golden_drift_check.py: it compares the
case names, their declaration order and the sha256 of every baseline file
against the committed test/golden_baseline.json. A green suite only proves
the model still works; identical hashes prove the numbers did not change.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.
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.