Custom-op bridge: ORT custom ops called by onnx-light#

onnx_light_ort::bridge_ops_ort_called_by_light is a shared library depending only on Shared support for hosting ORT-compatible plugins and the platform loader. It does not install, load or link ORT. onnx_light_ort.CustomOpSession accesses its small C ABI through ctypes from the source checkout. Its model objects come from onnx_light.onnx bindings built in the same tree, not the onnx package; no project artifact is installed into site-packages. Execution uses a real onnx-light RuntimeSession and context-local KernelBase factory, not a substitute Python implementation.

C++ consumers link only onnx_light_ort::bridge_ops_ort_called_by_light and include onnx_light_ort/bridge_ops_ort_called_by_light/host.h. The target publishes the repository and ORT API include paths; its implementation libraries remain private. A consumer test builds without the test helper’s additional include paths to verify this contract.

Initial audited profile#

This is not a general ORT compatibility claim. Plugins are trusted executable code, loaded only from an explicit caller-supplied path resolved before loading. They must be audited against this API subset; unsupported function-table slots are null and must not be called. Arbitrary plugin binaries are not safe to probe for compatibility by executing them.

The host accepts only API-18 descriptors, one required CPU input and output of matching float16, bfloat16, float32 or float64 type, a custom-domain opset import, and status-returning CreateKernelV2 and KernelComputeV2 callbacks with KernelDestroy. The initial model must contain exactly one custom node, with distinct graph input and output, no initializers and no local functions. Model data is limited to 16 MiB and each runtime tensor to 64 MiB, with rank at most 16 and checked nonnegative dimension products. Scalars and empty tensors are supported. The Python wrapper checks serialized model size before parsing bytes and input size and rank before copying arrays. These checks raise ValueError; native validation remains in place for C callers.

Registration rejects missing descriptors, unsupported devices or types, duplicate registrations for the same domain/name/type, incompatible opsets, optional/variadic arguments, alias callbacks and shape-inference callbacks. Concrete type overloads are selected by the model’s tensor type; generic UNDEFINED type descriptors are not supported. Legacy callbacks, EPs, logging/configuration, general allocator APIs and the remaining roadmap functions are not implemented. Output shapes are determined at execution.

Kernels are initialized lazily once per session node and retained across changing input shapes. Every call reads the current runtime context. The Python wrapper serializes runs and close operations, copies inputs to protect caller-owned arrays from the API’s mutable data accessor, and returns independent NumPy output copies. Output buffers are host-owned. This initial Python interface does not claim zero-copy execution.

LightOrtRunTyped accepts a raw input pointer, its ONNX element type and element count, and returns raw output storage of the same type. It checks the model type, element alignment and dtype-dependent byte limit before reading the input. The original LightOrtRun C ABI remains available for float32 and rejects models of another type. The Python wrapper uses the typed entry point, accepts NumPy float16/float32/float64 and ml_dtypes.bfloat16 arrays, and rejects arrays whose dtype differs from the model instead of converting them.

close() and the context-manager protocol release kernels before unloading the plugin. A finalizer also releases abandoned sessions. Registration and compute errors propagate as Python exceptions; there is no ORT fallback.

Tests and example#

The independent examples/custom_ops/ort_custom_ops.cc implements ort_custom_ops::LeakyRelu using only pinned ORT headers and its own numerical code, without linking onnx-light. This independent example remains float32-only. Native tests execute it under the standalone host, including scalar/empty inputs and API negotiation failure. Python tests check changing shapes, read-only and noncontiguous inputs, retained kernel state, independent sessions and outputs, close ordering, missing attributes, unsupported graphs, versions, names and dtypes. They assert that neither ORT nor the onnx package was imported.

Run ctest --test-dir build -L bridge_ops_ort_called_by_light --output-on-failure. Enable ONNX_LIGHT_ORT_TEST_PYTHON=ON for the Python cases. When the plugin target is enabled too, an additional Python suite loads the actual onnx_light::LeakyRelu and onnx_light_cpu::Log custom ops in all four types through the same standalone host. These share numerical cases with the real-ORT suite: SIMD tails, changing shapes, scalars, empty tensors, near-one and subnormal inputs, non-finite values, read-only inputs and recovery after type/rank errors. The host remains generic; it does not link the outgoing bridge or import ORT to execute this kernel. See Custom ops: ORT called by onnx-light for the executable example and Development for build and environment setup.