/* * nx_backend — the NORMATIVE Embedda accelerator C ABI (v1, FROZEN). * * This header is the vendor-facing contract of the Backend SDK: implement * these symbols in a shared library (`libyourvendor.so`) and the dynamic * loader (`nexus-backend-dyn`, gate NEXUS_BACKEND=dyn) can drive your * accelerator. It is the neutral twin of the two shipped shims — * libs/hailo/hailo-infer-shim/csrc/nx_hailo.h (Hailo NPU) and * libs/gpu/nexus-trt-shim/csrc/nx_trt.h (TensorRT GPU) — and * tools/abi-parity/check.sh enforces that all THREE stay isomorphic modulo * the family prefix (nx_be_ / nx_hailo_ / nx_trt_). The shipped shims do NOT * change because of this file; this is the frozen normative copy * (contracts/FROZEN.sha256). * * ── Execution model (what your implementation must honour) ───────────────── * * A ring of S slots, each owning buffers for EVERY model input and output. * `submit` copies the caller's frame into a free slot's input buffer(s) and * launches asynchronous inference; the completion enqueues the slot; * `poll` hands a completed slot to the caller to read float32 outputs; * `release` recycles it. Up to S inferences stay in flight. * * - All outputs are FLOAT32 (dequantized by the backend), NHWC layout. * - `submit` BLOCKS while all slots are busy, bounded by `timeout_ms`; * on timeout it returns <0 (an error the caller sees — never a silent drop). * - `poll(block_ms)`: 1 = a slot completed; 0 = nothing within `block_ms` * (block_ms < 0 waits forever); <0 = a job FAILED — report it, do not * swallow failed jobs by returning 0. * - Buffer lifetime: the pointer from `*_output(slot, idx)` is valid ONLY * between the poll that returned `slot` and `*_release(slot)`. After * release the backend may reuse the memory. Callers copy out before * releasing; implementations must not free slot buffers before `*_close`. * - `release` of a slot that is not caller-owned (never polled, or already * released) must be ignored or logged — it must NOT corrupt the ring. * - Threading: one caller thread drives submit/poll/output/release (the * shipped shims assume this); `*_close` must not race in-flight jobs — * implementations drain or cancel first. * * ── Honesty on telemetry ──────────────────────────────────────────────────── * * `*_chip_temperature` / `*_power_milliwatts` return <0 when the device does * NOT report the metric. NEVER fabricate a value: the node surfaces `null` * for a chip that doesn't report — a plausible-looking invented number is a * contract violation (see the project honesty rule). */ #ifndef NX_BACKEND_H #define NX_BACKEND_H #include #include #ifdef __cplusplus extern "C" { #endif /* Opaque inference context (one loaded + configured model + its slot ring). */ typedef struct nx_be_ctx nx_be_ctx; /* Describes one model output tensor (FLOAT32, NHWC). */ typedef struct { uint32_t height; uint32_t width; uint32_t features; uint32_t num_floats; /* total float32 elements in the output buffer */ int32_t is_nms; /* 1 if a hardware-NMS op output, else 0 (raw conv) */ uint32_t nms_classes; /* number of classes when is_nms == 1 */ } nx_tensor_info; /* * Open: load the model artifact at `model_path` (vendor-specific format: * .hef, .engine, …), configure it, and build a ring of `slots` slots. * `slots <= 0` auto-sizes to the backend's preferred queue depth, clamped to * a sane [1, N]. Returns NULL on ANY failure (missing file, bad model, * device unavailable) — never a half-initialised context. */ nx_be_ctx *nx_be_open(const char *model_path, int slots); /* First input dims (width, height, channels). For multi-input models every * input served by plain `submit` gets the same frame copied. 0 / <0. */ int nx_be_input_dims(const nx_be_ctx *ctx, uint32_t *width, uint32_t *height, uint32_t *channels); /* Per-input dims (width, height, channels) for input `idx`. 0 / <0. */ int nx_be_input_info(const nx_be_ctx *ctx, size_t idx, uint32_t *width, uint32_t *height, uint32_t *channels); /* Input `idx`'s name, NUL-terminated into `buf` (truncated to `cap`), so the * caller can route the right crop to it. Full name length, or <0. */ int nx_be_input_name(const nx_be_ctx *ctx, size_t idx, char *buf, size_t cap); /* Counts. 0 if ctx is NULL. */ size_t nx_be_num_inputs(const nx_be_ctx *ctx); size_t nx_be_num_outputs(const nx_be_ctx *ctx); size_t nx_be_slots(const nx_be_ctx *ctx); size_t nx_be_pending(const nx_be_ctx *ctx); /* Output tensor descriptor for output `idx`. 0 on success, <0 on error. */ int nx_be_output_info(const nx_be_ctx *ctx, size_t idx, nx_tensor_info *info); /* Copy output `idx`'s name into `buf` (NUL-terminated, truncated to `cap`). * Returns the full name length, or <0 on error. */ int nx_be_output_name(const nx_be_ctx *ctx, size_t idx, char *buf, size_t cap); /* * Submit a packed frame (exactly width*height*channels bytes) for async * inference. Copies the frame into every input buffer of a free slot and * launches the job. Blocks until a slot is free (bounded by `timeout_ms`). * `user_tag` is echoed back by poll. Returns 0 on success, <0 on error * (including timeout waiting for a slot, and a frame_len mismatch). */ int nx_be_submit(nx_be_ctx *ctx, const uint8_t *frame, size_t frame_len, uint64_t user_tag, int timeout_ms); /* * Submit with a DISTINCT buffer per model input (cascade secondaries feed a * different crop per input). Requires exactly one non-null buffer of * sufficient size per model input. Otherwise identical to nx_be_submit * (free-slot wait, async launch, `user_tag` echoed by poll). Returns 0 / <0. */ int nx_be_submit_multi(nx_be_ctx *ctx, const uint8_t *const *bufs, const size_t *lens, size_t n_bufs, uint64_t user_tag, int timeout_ms); /* * Wait for the next completed inference. On success returns 1 and sets * *out_user_tag + *out_slot (the slot handle to read with nx_be_output). * Returns 0 if nothing completed within block_ms (block_ms<0 = wait forever), * or <0 on a job failure. The slot stays owned by the caller until released. */ int nx_be_poll(nx_be_ctx *ctx, uint64_t *out_user_tag, int *out_slot, int block_ms); /* Pointer to slot `slot`'s output `idx` float32 buffer (num_floats elements). * Valid only between the poll that returned `slot` and nx_be_release(slot). * NULL on bad indices. */ const float *nx_be_output(const nx_be_ctx *ctx, int slot, size_t idx); /* Return a polled slot to the free ring so it can accept new submissions. */ void nx_be_release(nx_be_ctx *ctx, int slot); /* Release the context: shut down inference, free slot buffers. */ void nx_be_close(nx_be_ctx *ctx); /* * Device telemetry (best-effort, for the node dashboard). Control-path: must * work while inference runs. */ /* Junction temperature in °C. Writes *out_celsius and returns 0 on success; * <0 if the device does not report it (NEVER a fabricated value). */ int nx_be_chip_temperature(const nx_be_ctx *ctx, float *out_celsius); /* Instantaneous board power in milliwatts. Writes *out_mw and returns 0; * <0 if power measurement is unsupported (NEVER a fabricated value). */ int nx_be_power_milliwatts(const nx_be_ctx *ctx, float *out_mw); #ifdef __cplusplus } #endif #endif /* NX_BACKEND_H */