Loading a model
Hydronnx's user-facing workflow is three steps: inspect, emit, use. You
point one CLI at your .onnx to confirm Hydronnx can handle it, point the
other CLI at the same file to generate a Chelis module, then import that
module like any other Chelis code.
This document walks through the workflow concretely against the worked example
at examples/tabular_classifier/. Every command and every expected line is
verified against the CLIs.
The three-step workflow
Section titled “The three-step workflow” +--------------------------+ model.onnx | chelis-hydronnx-inspect | inventory + readiness report +--------------------------+ | +--------------------------+ model.onnx | chelis-hydronnx-emit | Hydronnx.<Name>.ch +--------------------------+ | +--------------------------+ .ch | chelis check / test / | type-checked Chelis program | prove | +--------------------------+The two CLIs are independent. inspect is read-only, handy for "is this model
in scope, what is its surface, will grad work", but you can skip straight to
emit if you already know the answers. emit re-runs the parse internally; it
does not depend on inspect.
1. Inspect
Section titled “1. Inspect”chelis-hydronnx-inspect reads the .onnx, validates the opset and dtype set,
and prints a human-readable inventory.
chelis-hydronnx-inspect examples/tabular_classifier/iris_classifier.onnxThe full output is shown in the CLI reference. What to look at, in order:
- The header.
opset_version: 17is supported (Hydronnx accepts opsets 11 through 23).producer: gen_tabular_model.pyis informational. inputs/outputs. The example showsfeatures : f32 [1, 4]mapping toprobabilities : f32 [1, 3]. The dtypes (f32,f64,i32,i64, the supported set) and the static shapes ([1, 4]) tell you what type the emittedforwardwill have.nodes. Four nodes:Gemm,Relu,Gemm,Softmax, all in the emit set. A node carrying a deferred operator (Round,MultiHeadAttention,ScatterND, orScatterElements) would appear here, the inspect would still succeed, andemitwould reject.Conv,ConvTranspose,MaxPool,AveragePool,Cos,Tan,Floor, andCeilemit. The deferral list is in Supported ONNX surface.weights. Four initializers. The example is small enough (a few hundred bytes of.onnx) that the emitted weight literals stay tiny. A multi-megabyte model is out of scope for the inline-emit path; see Weights and large models.non-differentiable operators. The detector's per-node verdict. A non-empty list here is a strong signal thatchelis gradwill fail; an empty list (the example's case) is not a guarantee. See Limitations and scope.
If inspect exits non-zero, the model is out of scope, and emit will produce
the same error. Common cases are catalogued in
Error handling.
2. Emit
Section titled “2. Emit”chelis-hydronnx-emit turns the same .onnx into a .ch Chelis module.
chelis-hydronnx-emit \ examples/tabular_classifier/iris_classifier.onnx \ -o examples/tabular_classifier/src/irisclassifier.chOutput:
wrote examples/tabular_classifier/src/irisclassifier.chWhat emit produced:
module Hydronnx.IrisClassifierexport (forward)-- hydronnx - generated Chelis module. Do not edit by hand.-- ... provenance metadata ...def w1() = reshape(to_tensor([...]), [cast(4, int64), cast(6, int64)])def b1() = to_tensor([...])def w2() = ...def b2() = ...sig forward: tensor[1, 4, f32] -> tensor[1, 3, f32]def forward(features) = { h1_pre = add(matmul(features, w1()), expand(b1(), cast(0, int32), cast(1, int32))) h1 = relu(h1_pre) -- ... ... probabilities}The two lines that matter for downstream code are the module declaration and
the sig:
- The module is
Hydronnx.IrisClassifier. Your own Chelis filesimport Hydronnx.IrisClassifier (forward). - The signature is
tensor[1, 4, f32] -> tensor[1, 3, f32], the same shapes and dtypes theinspectstep reported. From this point on,forwardis a typed Chelis function. Pass it atensor[1, 5, f32]andchelis checkrejects the call at compile time.
The emit is deterministic. Re-running on the same .onnx regenerates the file
byte-for-byte, modulo the -- emitted: timestamp and -- source: path lines.
The worked example's acceptance test (tests/h5_example.rs) checks exactly
that, so if emit ever drifted, the test would catch it.
A few emit options you may want
Section titled “A few emit options you may want”The CLI's --out / -o, --module-name, --precision, and --batch-dim
flags are documented in the
CLI reference. A reminder of the
warn-only ones:
--precision f64and--batch-dim Nare plumbed but inert. They print a loudwarning:to stderr and proceed; the emitted.chis what you would have gotten without the flag. Do not use them expecting effect.
Symbolic batch dimensions
Section titled “Symbolic batch dimensions”If your ONNX model declares a symbolic batch dim (an ONNX dim_param), that dim
survives into the emitted sig. For example,
tests/fixtures/e2e/symbolic_batch.onnx (a single Relu over a [batch, 5]
input) emits:
sig forward: tensor[batch, 5, f32] -> tensor[batch, 5, f32]def forward(x: tensor[batch, 5, f32]) = { y = relu(x) y}A caller is then either also generic over batch, and unifies, or provides a
concrete batch value at the call site. Rank-2 MatMul and default Gemm
preserve a symbolic graph-input batch dim, including the common rank-1 Gemm bias
broadcast. Runtime-dim reshape supports ONNX 0 dims copied from symbolic
graph inputs; -1 inference with symbolic dims rejects. See
Limitations and scope.
3. Use
Section titled “3. Use”The emitted module is ordinary Chelis. The worked example's src/predict.ch is
the user-written code:
module Hydronnx.Predictimport Hydronnx.IrisClassifier (forward)export (feature_scale, normalize, top_class, classify)
def feature_scale() = reshape( to_tensor([cast(0.1, f32), cast(0.1, f32), cast(0.1, f32), cast(0.1, f32)]), [cast(1, int64), cast(4, int64)],)
sig normalize: tensor[1, 4, f32] -> tensor[1, 4, f32]def normalize(raw) = mul(raw, feature_scale())
sig top_class: tensor[1, 3, f32] -> tensor[1, int64]def top_class(probs) = argmax_reduce(probs, cast(1, int32))
sig classify: tensor[1, 4, f32] -> tensor[1, int64]def classify(raw) = top_class(forward(normalize(raw)))Three things to notice:
forwardis imported and called like any other function. The compositiontop_class(forward(normalize(raw)))is type-checked end to end. If the intermediate shapes did not line up,chelis checkwould reject this line at compile time. There is no runtime shape surprise.feature_scale()is hand-written: nothing in the Hydronnx workflow forces a particular pre-process. The emittedforwardis a function, not a pipeline.top_classends inargmax_reduce. The composedclassifytherefore has an int64 output and is no longer mathematically differentiable. That is not Hydronnx's choice; it is the post-process the example author picked. See Composition with Chelis for more on building pipelines, and Properties and verification for attaching@propertyblocks to the emittedforward.
The example is a complete reef project. From its directory:
cd examples/tabular_classifierchelis testBoth example tests pass, producing 2 passed, 0 failed. That confirms the
inspect, emit, use loop runs end to end on a real model.
What goes wrong, and how to recover
Section titled “What goes wrong, and how to recover”If any step exits non-zero, see Error handling for the full
error catalogue: which HydronnxError variants exist, what each one's message
looks like, what tends to cause it, and how to resolve it.
The most common cases:
- Unsupported operator. Your model contains one of the deferred operators. Hydronnx prints which operator and where. The deferral is documented in Supported ONNX surface.
- Unsupported opset. Your model is older than opset 11 or newer than 23.
Re-export at a supported opset (most exporters take an
opset_versionargument). - Unsupported dtype. Your model uses an ONNX dtype outside the supported set
(
f32,f64,i32,i64,bool). Cast at export time.
A model that inspects clean but does not emit cleanly is rare. Almost always
it is a parsed-but-unsupported operator that the inspect path tolerates and the
emit path rejects.