The KServe client speaks the KServe V2 / Open Inference Protocol (OIP), so it is wire-compatible in principle with any server that implements it. This document records the combinations that are actually exercised by CI and how, so the matrix stays honest rather than aspirational.
The protocol client itself lives in the standalone
neuriplo-kserve-clientrepo (fetched here viaFetchContent); neuriplo-infer keeps theKserveEngineadapter and this integration harness.
It is kept green by the integration harness
app/test/kserve_integration.sh and the
KServe Integration workflow:
- Dry-run (every push/PR, no images/GPU): validates that the client driver
and server launch commands are well-formed for each server × transport. Also
registered with CTest as
kserve_integration_dry_run(runs by default). - Live (manual
workflow_dispatchwithrun_live=true): starts Triton and OVMS containers serving a tiny shared ONNXIdentitymodel and asserts a successful KServe V2 inference round-trip over HTTP and gRPC against each.
Legend: ✅ exercised live in CI · 🟡 dry-run only (live behind manual dispatch) · ⏳ not yet covered.
| Server | HTTP | gRPC | Notes |
|---|---|---|---|
| neuriplo-kserve-runtime | ✅ validated | ✅ validated | reference target; gRPC live-validated 2026-06-12 via client conformance + infer KserveEngine after runtime PR #9 (raw_output_contents) |
| Triton Inference Server | 🟡 (live: dispatch) | 🟡 (live: dispatch) | ONNX backend; grpcurl over proto/kserve_grpc.proto |
| OpenVINO Model Server (OVMS) | 🟡 (live: dispatch) | 🟡 (live: dispatch) | same OIP endpoints; serves ONNX directly |
| TorchServe (OIP) | ⏳ | ⏳ | version routing varies |
"Live: dispatch" means the round-trip is implemented and run by the live job, which is gated behind a manual dispatch so routine CI never pulls multi-GB server images. The dry-run path for these servers runs on every PR.
The reference neuriplo-kserve-runtime delegates execution to neuriplo
backends, so it serves whatever format the backend it was built with consumes
(ONNX, OpenVINO IR, TorchScript, TFLite, ...). It reports the serving backend
as platform: neuriplo_<backend>, which KserveEngine surfaces and the CLI
folds into the output filename (processed_<model>_kserve_<backend>.png).
TFLite over the litert backend was validated locally on 2026-06-13
(runtime + neuriplo-infer client round-trip on a .tflite model — YOLO26,
which serves correctly); it is not yet wired into the CI live job. Triton and
OVMS in the matrix above serve ONNX.
TL;DR — do not serve
ecdetas TFLite. It loads and runs end-to-end but the detections are numerically garbage. The fault is the ONNX→TFLite conversion, not the runtime, the custom ops, or the serving path.
ecdet_s is an RT-DETR/D-FINE-style detector (deformable attention +
transformer: ~38 LayerNorm, ~148 MatMul, ~115 Transpose). Converting it with
onnx2tf (flatbuffer_direct backend,
the default) produces a .tflite that runs but is numerically wrong. To
even get it to run, the litert backend had to gain two kernels (see
neuriplo/backends/litert/src/LiteRTInfer.cpp):
- a custom
ONNX_GRIDSAMPLEkernel (onnx2tf's lowering of ONNXGridSample; NCHW, bilinear /zeros/align_corners=false), and - a
SIGNbuiltin override that also accepts INT64 (the stock TFLite kernel is float-only; onnx2tf's own evaluator dies on the same node).
The GridSample kernel was validated against onnxruntime to ~2e-5, so it is
not the problem. The problem is conversion fidelity in the transformer body
(onnx2tf's static shapes in the deformable region are even internally
inconsistent; the alternative tf_converter backend crashes outright on
NCHW→NHWC layout bugs).
Verified end-to-end through the C++ neuriplo-infer client (same pre/post-
processing, identical data/dog.jpg input), conf threshold 0.5:
| Backend (C++ e2e) | Top detections |
|---|---|
| TensorRT (engine) / ONNX (onnxruntime) | bicycle 0.93, dog 0.92, car 0.74 ✅ |
KServe litert (this .tflite) |
top score ≈0.19, random labels, nonsense boxes ❌ |
Recommendation: serve ecdet via ONNX/TensorRT/OpenVINO, not TFLite. If
TFLite is required, the model must be re-exported in a TFLite-friendly form (or
converted with a fixed onnx2tf) — there is nothing to fix in this repo. Simpler
CNN detectors (e.g. YOLO26) convert and serve correctly over litert; the
issue is specific to the deformable/transformer architecture.
Input datatypes are taken from the server's model metadata (not hardcoded). The
adapter (KserveEngine) decodes outputs into the
TensorElement variant (float / int32 / int64 / uint8), widening or
narrowing wider server datatypes.
| KServe datatype | HTTP | gRPC | Decoded as |
|---|---|---|---|
| FP32 | ✅ | ✅ | float |
| FP64 | ✅ | ✅ | float (narrowed) |
| FP16 | ✅ | ✅ | float (gRPC via raw tensor contents — the default; the typed-contents fallback KSERVE_BINARY=0 cannot carry it) |
| INT8 / INT16 / INT32 | ✅ | ✅ | int32 |
| INT64 | ✅ | ✅ | int64 |
| UINT8 / BOOL | ✅ | ✅ | uint8 |
| UINT16 / UINT32 / UINT64 | ✅ | ✅ | int64 (widened) |
The integration harness round-trips FP32 end-to-end (the datatype shared by
the tiny model on both servers); the wider datatype decoding is covered by the
KserveProtocol / KserveEngine unit tests.
- Bearer token via
KSERVE_BEARER_TOKEN(HTTPAuthorization: Bearer …, gRPC call metadata). - TLS selection by scheme (
https:///grpcs://); HTTPS requires an OpenSSL-enabled build. Optional mTLS viaKSERVE_CLIENT_CERT/KSERVE_CLIENT_KEY; the CA bundle comes fromKSERVE_CA_CERT(system roots when unset).
The client implements the KServe V2 Model Repository extension on both
transports — repositoryIndex() / loadModel(name) / unloadModel(name)
(HTTP POST /v2/repository/index|models/{m}/load|models/{m}/unload; gRPC
RepositoryIndex / RepositoryModelLoad / RepositoryModelUnload). This is an
optional server capability: Triton exposes it (with
--model-control-mode=explicit), and KServe/OVMS support varies by deployment,
so it is not part of the routine integration round-trip. The pure path
builders and index parser are unit-tested; the wire calls reuse the existing
retry/auth/TLS plumbing. See docs/KserveRuntime.md.
# Dry-run (no docker needed) — what CI runs on every PR:
bash app/test/kserve_integration.sh --dry-run
# Live round-trip against both servers (needs docker + curl + grpcurl + onnx):
bash app/test/kserve_integration.sh --live
# Narrow it down:
bash app/test/kserve_integration.sh --live --servers triton --transports http
# Live client <-> neuriplo-kserve-runtime (sibling repo; HTTP + gRPC):
# ../neuriplo-kserve-client/scripts/runtime_conformance.sh --liveSee docs/KserveRuntime.md for the full KServe runtime reference (architecture, capabilities, configuration, build modes).