| name | add-function-body |
| description | Add a function body definition to an ONNX operator, defining how it decomposes into simpler ops. Use when asked to make an op decomposable, add a FunctionBody, implement SetContextDependentFunctionBodyBuilder, or express an op in terms of other ONNX operators. |
Follow the full guide in docs/AddFunctionBody.md.
File Locations
| Component | File |
|---|
| Function body definition | onnx/defs/<domain>/defs.cc (inline with schema) |
| FunctionBuilder utilities | onnx/defs/function.h |
| Function tests | onnx/test/cpp/function_get_test.cc, onnx/test/cpp/function_verify_test.cc |
Method 1: Simple String-Based Function Body
ONNX_OPERATOR_SET_SCHEMA(
LessOrEqual, 16,
OpSchema()
.TypeAndShapeInferenceFunction(inferenceFunction)
.FunctionBody(R"ONNX(
{
O1 = Less (A, B)
O2 = Equal (A, B)
C = Or (O1, O2)
}
)ONNX"));
With explicit opset version:
.FunctionBody(R"ONNX(...)ONNX", 18)
Referencing attributes with @attr_name:
.FunctionBody(R"ONNX(
{
Alpha = Constant <value_float: float = @alpha>()
AlphaCast = CastLike (Alpha, X)
...
}
)ONNX")
Method 2: Context-Dependent Function Body
For ops whose decomposition varies based on attributes or optional inputs:
static bool BuildFunctionBodyMyOp(
const FunctionBodyBuildContext& ctx,
const OpSchema& schema,
FunctionProto& functionProto) {
FunctionBuilder builder(functionProto);
builder.Add("output = SomeOp (input)");
schema.BuildFunction(functionProto);
return true;
}
.SetContextDependentFunctionBodyBuilder(BuildFunctionBodyMyOp)
FunctionBuilder API
FunctionBuilder builder(functionProto);
builder.Add("Y = Relu (X)");
builder.Const("alpha", std::vector<float>{0.01f});
builder.Const1D("axes", int64_t(1));
builder.Add(R"( // Multi-line
X_Sub = Sub (X, X_Max)
X_Exp = Exp (X_Sub)
)");
builder.AddOpset("", 18);
schema.BuildFunction(functionProto);
return true;
Multiple Opset Versions
.SetContextDependentFunctionBodyBuilder(builderForOpset13)
.SetContextDependentFunctionBodyBuilder(builderForOpset18, 18)
ONNX Function Body Syntax
Function body strings use the ONNX text format ("onnxtxt"). See the onnxtxt skill for the full syntax cheat sheet, attribute references (@attr_name), Constant <value = ...> forms, CastLike vs Cast, body-subgraph idioms, and parser tests. Quick reminders specific to function bodies:
- Variable names are local intermediates; input/output names must match the schema's declared names.
- Reference enclosing-op attributes with
@attr_name — and only those declared in .Attr(...) calls.
- Use
CastLike (not Cast) when the target type depends on another input.
Code Style: Prefer Named Functions
Define context-dependent function body builders as separate named functions rather than inline lambdas within ONNX_OPERATOR_SET_SCHEMA. The macro expansion makes setting breakpoints on inline lambdas unreliable in debuggers.
Simple string-based .FunctionBody(R"ONNX(...)ONNX") definitions don't have this issue.
After Making Changes
python onnx/defs/gen_doc.py
lintrunner -a --output oneline
Common Mistakes
- Forgetting
schema.BuildFunction(functionProto) at end of context-dependent builders
- Forgetting to
return true from the builder function
- Variable names conflicting with input/output names
- Using
Cast instead of CastLike for dynamic type matching
- Function body not producing all declared outputs
- Using
@attr_name for an attribute not declared in .Attr() calls