What Are ONNX Runtime Contrib Operators and How to Register Custom Ones?
Contrib operators are ONNX Runtime-specific extensions in the Microsoft domain (kMSDomain) that extend beyond the ONNX specification, while custom operators are user-defined kernels that you register at runtime via the Ort::CustomOpDomain API.
ONNX Runtime ships with hundreds of built-in operators, but real-world models often require functionality outside the official ONNX specification. In the microsoft/onnxruntime repository, contrib operators provide Microsoft-supported extensions maintained in the com.microsoft domain, while the custom operator API allows you to inject your own kernels at runtime without recompiling the framework.
Understanding Contrib Operators in ONNX Runtime
Contrib operators are implementation-specific extensions that reside in the Microsoft domain (kMSDomain) rather than the standard ONNX namespace. These operators are maintained by Microsoft and partners to expose bleeding-edge functionality before it becomes part of the official ONNX spec.
According to the source code in onnxruntime/core/graph/contrib_ops/contrib_defs.h, contrib operator schemas are registered globally by the RegisterContribSchemas() function implemented in onnxruntime/core/graph/contrib_ops/contrib_defs.cc. The concrete kernel implementations are grouped by execution provider in files such as onnxruntime/contrib_ops/cpu/cpu_contrib_kernels.cc, where each provider calls Register*ContribKernels() to bind schemas to implementations.
To use a built-in contrib op, reference it in your model with the domain string com.microsoft (defined internally as kMSDomain). Common examples include LayerNormalization and Range, which are registered alongside their shape-inference logic in the contrib schema registry.
How to Register Custom Operators in ONNX Runtime
When you need functionality that contrib operators do not provide, you can register custom operators using the C++ or C API. The registration flow follows the same pattern as internal operators but uses the public OrtCustomOpDomain APIs instead of the internal registry.
Step 1: Define the Operator Schema and Kernel
Create a struct that inherits from Ort::CustomOpBase. You must implement virtual methods to declare the operator's name, input/output counts, and data types. The test harness in onnxruntime/test/shared_lib/custom_op_utils.h provides the canonical pattern for this.
struct MyAddOp : Ort::CustomOpBase<MyAddOp, MyAddKernel> {
void* CreateKernel(const OrtApi& api, const OrtKernelInfo*) const {
return new MyAddKernel(api);
}
const char* GetName() const noexcept { return "MyAdd"; }
size_t GetInputTypeCount() const noexcept { return 2; }
ONNXTensorElementDataType GetInputType(size_t) const noexcept {
return ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT;
}
size_t GetOutputTypeCount() const noexcept { return 1; }
ONNXTensorElementDataType GetOutputType(size_t) const noexcept {
return ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT;
}
};
struct MyAddKernel {
MyAddKernel(const OrtApi& api) : api_(api), ort_(api_) {}
void Compute(OrtKernelContext* context) {
const OrtValue* a = ort_.KernelContext_GetInput(context, 0);
const OrtValue* b = ort_.KernelContext_GetInput(context, 1);
// Perform element-wise addition, allocate output, fill it
}
const OrtApi& api_;
Ort::CustomOpApi ort_;
};
Step 2: Create a Custom Domain
Instantiate an Ort::CustomOpDomain (or the C API OrtCustomOpDomain*) to namespace your operators. This prevents collisions with built-in operators and contrib operators.
Ort::CustomOpDomain custom_domain("my.custom.domain");
Step 3: Add the Operator to the Domain
Register your operator instance with the domain using Add(). The instance must outlive the session. In the C API, this corresponds to OrtApis::CustomOpDomain_Add.
MyAddOp my_add_op;
custom_domain.Add(&my_add_op);
Step 4: Attach the Domain to Session Options
Pass the domain to your OrtSessionOptions using the Add method. The C API equivalent is OrtApis::AddCustomOpDomain. When you create the session, ONNX Runtime will resolve any nodes with your custom domain and dispatch them to your kernel implementation.
Ort::Env env(ORT_LOGGING_LEVEL_WARNING, "example");
Ort::SessionOptions session_options;
session_options.Add(custom_domain);
Ort::Session session(env, "model_with_myadd.onnx", session_options);
Alternative: Register via Shared Library (C API)
For dynamic loading, compile your operators into a shared library that exports a RegisterCustomOps function. Use OrtRegisterCustomOpsLibrary to load it at runtime, which internally uses CreateCustomRegistry from onnxruntime/core/session/custom_ops.h to bundle the domains.
OrtSessionOptions* opts;
OrtCreateSessionOptions(&opts);
/* Load a shared library that exports:
* OrtStatus* RegisterCustomOps(OrtSessionOptions* options, const OrtApiBase* api_base);
*/
OrtRegisterCustomOpsLibrary(opts, "/path/to/my_custom_ops.so", NULL);
OrtSession* sess;
OrtCreateSession(env, "model.onnx", opts, &sess);
Key Implementation Files in ONNX Runtime
The following files in microsoft/onnxruntime define the contrib and custom operator infrastructure:
onnxruntime/core/graph/contrib_ops/contrib_defs.h– DeclaresRegisterContribSchemas()and the contrib operator registry API.onnxruntime/core/graph/contrib_ops/contrib_defs.cc– Implements the registration of built-in contrib schemas such asLayerNormalizationandRange.onnxruntime/contrib_ops/cpu/cpu_contrib_kernels.cc– Registers CPU kernel implementations for contrib operators viaKernelRegistry::Register.onnxruntime/core/session/custom_ops.h– ContainsCreateCustomRegistryand theOrtCustomOpDomainhelpers used by the C API when saving models with custom ops.onnxruntime/test/shared_lib/custom_op_utils.h– Provides reference implementations likeMyCustomOpthat demonstrate the expected struct pattern for custom op definitions.onnxruntime/python/tools/pytorch_export_contrib_ops.py– Shows how to emit models referencing contrib ops from Python, useful for model conversion pipelines.
Summary
- Contrib operators are Microsoft-maintained extensions in the
com.microsoftdomain (kMSDomain), registered viaRegisterContribSchemas()and implemented per execution provider in files likecpu_contrib_kernels.cc. - Custom operators are user-defined kernels that require implementing
Ort::CustomOpBase, creating anOrt::CustomOpDomain, and attaching it toOrtSessionOptionsbefore session creation. - The
CreateCustomRegistryutility incustom_ops.hbundles custom domains for internal use by the Standalone Op Invoker when persisting schemas to ORT format. - Both registration methods allow ONNX Runtime to execute operators outside the standard ONNX specification, whether built-in or user-defined.
Frequently Asked Questions
What is the difference between contrib operators and custom operators?
Contrib operators are built into ONNX Runtime and maintained in the Microsoft domain (kMSDomain), with schemas defined in contrib_defs.cc and kernels in provider-specific files like cpu_contrib_kernels.cc. Custom operators are defined by users at runtime through the Ort::CustomOpDomain API without modifying the ONNX Runtime source code, allowing you to extend functionality for specific models.
How do I load a custom operator library in Python?
Use the register_custom_ops_library method on onnxruntime.SessionOptions in the Python API. This wraps the underlying OrtRegisterCustomOpsLibrary C API call and loads your shared library containing the RegisterCustomOps export, making your custom ops available to the inference session.
Why are contrib operators placed in the com.microsoft domain?
The com.microsoft domain (internally kMSDomain) isolates Microsoft-specific extensions from the standard ONNX operator set, ensuring that models using these ops are explicitly marked as requiring ONNX Runtime rather than being portable to all ONNX-compliant frameworks. This distinction is enforced by the schema registration logic in contrib_defs.cc.
Can custom operators be used with any execution provider?
Yes. Once registered via OrtSessionOptions, custom operators are available to all execution providers in the session. However, your kernel implementation must be compatible with the provider's execution context—CUDA kernels for the CUDA provider, CPU kernels for the CPU provider, etc.—as the runtime dispatches the operator to the appropriate kernel based on the provider configuration.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →