Compact GraphBuilder authoring and runtime walkthroughs#

Date:

2026-08

discussion

Objective#

The objective is to make small ONNX graphs concise to author, easy to inspect, and easy to execute from the regular documentation. The work has two connected parts:

  • add a compact Python facade over the existing GraphBuilder primitives, inspired by yet-another-onnx-builder;

  • add non-gallery documentation that follows a model from construction or a backend-test case through compact display, optimization, and runtime execution.

The existing make_input, make_output, make_initializer, and make_node methods remain the stable low-level API. The compact facade delegates to them so opset resolution, schema validation, incremental shape inference, naming, and serialization continue to have one implementation.

Compact authoring contract#

GraphBuilder.op exposes ONNX operators by their canonical CamelCase names:

x = g.inp("X", TensorProto.FLOAT, [None, 4])
bias = g.init(numpy.array([1, 2, 3, 4], dtype=numpy.float32), name="bias")
added = g.op.Add(x, bias)
result = g.op.Relu(added, outputs="Y")
custom = g.op.CustomNormalize(
    result, domain="com.example", outputs="Z", epsilon=1e-5
)
g.out(custom, TensorProto.FLOAT, [None, 4])

The facade follows these rules:

  • g.op.<Operator>(*inputs, **kwargs) forwards canonical ONNX attributes from kwargs and returns one output name or a tuple of output names;

  • outputs controls an explicit output name, a list of names, or an output count; domain and name remain reserved node options;

  • None represents an omitted optional ONNX input, variadic inputs remain positional, and NumPy arrays may be converted to named initializers;

  • standard-domain operators use the builder’s configured opset and unknown standard operators fail explicitly;

  • custom operators use the same g.op.<Operator> surface with an explicit non-empty domain; the domain and version must already exist in the builder’s opset imports and are never registered silently;

  • a custom operator does not require a locally registered schema. When no schema is available, attributes are preserved without schema inference and outputs must specify every output for operators that do not have exactly one output;

  • g.inp(name, elem_type, shape) creates and returns an input name;

  • g.out(name, elem_type=None, shape=None) declares a graph output and returns its name;

  • g.init(value, name=None) adds an initializer, generates a name when necessary, and returns the final name.

Operator lookup should use one cached proxy and __getattr__ rather than generating one Python method per schema. Reserved options must be separated from ONNX attributes before calling the existing node builder. Multi-output, optional-input, variadic-input, custom-domain, custom-operator, unknown-standard-operator, duplicate-name, and invalid-attribute behavior require direct tests.

The compact aliases are additive. Code generated by onnx_light.tools.translate continues to use the explicit make_* API until the compact facade is stable and can reproduce every generated construct.

GraphBuilder documentation#

Add docs/howto/graph_builder_basics.rst as a regular RST page, not a Sphinx-Gallery example. It should be linked from the how-to index and from the builder design section. The page covers:

  1. creating a builder and selecting opsets;

  2. declaring inputs, outputs, and initializers with the compact aliases;

  3. adding standard, variadic, optional-input, multi-output, and custom-domain operators, including a custom operator without a local schema;

  4. exporting and checking a model;

  5. running the existing GraphGraph pattern optimization;

  6. inspecting the optimization report and replaying its LocalRewriting records.

Every snippet must execute during documentation testing or be covered by an equivalent unit test. The page should show the compact API first and link to the explicit make_* methods as the complete low-level contract.

LocalRewriting display#

Improve LocalRewriting presentation without changing the data consumed by replay():

  • add a deterministic one-line summary containing the pattern, graph path, and matched/added node counts;

  • add a structured multiline representation grouping matched nodes, added nodes and positions, removed initializers, value renames, and timings;

  • display the root graph consistently as <root> and nested graph paths in execution order;

  • expose the short form through Python repr() and the detailed form through str() or an explicitly named detail method;

  • keep node ordering, field values, replay semantics, and serialization unchanged.

Tests compare stable structural lines rather than terminal width or color. Documentation output must remain readable without ANSI styling.

Runtime documentation walkthrough#

Add docs/howto/run_backend_test_case.rst outside Sphinx-Gallery. The page uses public APIs to perform one reproducible end-to-end flow:

  1. retrieve an exact backend-test case with onnx_light.onnx.backend.get_test_case;

  2. display its model with an existing onnx_light.tools representation, including the onnx-compact style;

  3. construct onnx_light.onnx.reference.ReferenceEvaluator;

  4. execute the supplied test inputs and compare the outputs with the expected backend-test values;

  5. repeat the run with representative verbose, events_enabled, and release_intermediates options, explaining the effect of each option.

The selected case must be small, deterministic, and identified by an exact stable name. The example must not download data or depend on optional large backend-test assets. If producing onnx-compact output currently requires a private helper, first expose or reuse one stable public function under onnx_light.tools rather than documenting internals.

The runtime design index should summarize the same execution path at the architecture level:

  • ReferenceEvaluator as the Python entry point;

  • RuntimeSession preparation, kernel resolution, and repeated execution;

  • RuntimeContext values, allocators, events, and intermediate release;

  • kernel registry dispatch and the session CPU executor;

  • the relationship between backend tests, the runtime how-to, and lower-level C++ APIs.

The design page remains conceptual and links to the runnable how-to for code.

Implementation sequence#

PR

Scope

Merge criterion

Status

Builder PR01

Compact op, inp, out, and init Python facade.

Existing make_* behavior is unchanged; compact calls cover standard, optional, variadic, multi-output, custom-operator, and invalid cases.

Planned

Builder PR02

Deterministic short and detailed LocalRewriting display.

Reports are readable in Python and plain documentation while replay data and behavior remain unchanged.

Planned

Builder PR03

Non-gallery GraphBuilder basics and optimization how-to.

The documented model builds, validates, optimizes, displays its rewrites, and round-trips in documentation tests.

Planned

Runtime PR01

Non-gallery backend-test runtime walkthrough and runtime design summary.

One stable case is displayed in onnx-compact form, executed, validated, and rerun with documented options; the design page explains the corresponding architecture.

Planned

Acceptance criteria#

The complete plan is accepted when:

  • compact authoring is additive and delegates to existing builder primitives;

  • generated models preserve opset imports, schema validation, names, shapes, and initializer ownership;

  • custom operators work through g.op with explicit domain/opset ownership, including when no local schema is registered;

  • LocalRewriting is readable without losing any replay information;

  • GraphBuilder construction and optimization have a runnable non-gallery how-to;

  • a backend-test model can be retrieved, rendered in compact form, executed, and validated from a second non-gallery how-to;

  • the runtime design section explains the architecture behind that example;

  • relevant Python/C++ tests, documentation builds, formatting, and linting pass.