CLI reference
Hydronnx ships three user-facing CLIs. All are Rust binaries; all read .onnx
and fail loudly on stderr with error: ... plus exit code 1 on any failure.
The one exception is shared by all three: a command-line usage error (an
unknown flag, or two flags that conflict) is reported by the argument parser in
its own format with exit code 2.
chelis-hydronnx-inspectprints a model's structure, operator inventory, and non-differentiable-operator report.chelis-hydronnx-emitgenerates a.chChelis module from a model.chelis-hydronnx-runexecutes a model with its real weights via the translate and eval path (no inline-weight size ceiling).
All commands shown here are verified against the binaries. --help output is
the source of truth; anything in this document that disagrees with --help is a
doc bug (please file an issue).
chelis-hydronnx-inspect
Section titled “chelis-hydronnx-inspect”Inspect an ONNX model file: print the model header, input and output tensors, node list, weight inventory, and the non-differentiable-operator report.
Usage: chelis-hydronnx-inspect <PATH>
Arguments: <PATH> Path to the .onnx file
Options: -h, --help Print helpOutput
Section titled “Output”A single text report on stdout, organized in sections. Run against the worked
example (examples/tabular_classifier/iris_classifier.onnx) the report is:
model: iris_classifierproducer: gen_tabular_model.pyir_version: 8opset_version: 17file_size: 606 bytes
inputs (1): features : f32 [1, 4]
outputs (1): probabilities : f32 [1, 3]
nodes (4): [gemm1] Gemm (features, W1, B1) [relu1] Relu (h1_pre) [gemm2] Gemm (h1, W2, B2) [softmax] Softmax (logits)
weights (4): W1 : f32 [4, 6] (96 bytes) B1 : f32 [6] (24 bytes) W2 : f32 [6, 3] (72 bytes) B2 : f32 [3] (12 bytes)
non-differentiable operators (0): none - this model contains no mathematically non-differentiable operators. NOTE: this section reports only *mathematical* non-differentiability and does not guarantee `grad` will succeed. Hydronnx verifies weighted Gemm AD, but other operator surfaces still need an explicit AD gate.Section by section:
- Header lines.
model:(ONNXmodel_name,(unnamed)if blank),producer:(ONNXproducer_name),ir_version:(the model's IR version),opset_version:(theai.onnxopset the model was exported at),file_size:(in bytes, read from the filesystem). inputs (N):. One line per graph input:<name> : <dtype> <shape>. Symbolic dims (ONNXdim_param) appear by name ([batch, 5]); static dims by value ([1, 4]); the rare ONNX "dynamic" dim with neither value nor name renders as?.outputs (N):. Same shape as inputs.nodes (N):. One line per node:[<name>] <op_type> (<inputs>). An unnamed node prints as[(unnamed)].weights (N):. One line per initializer:<name> : <dtype> <shape> (<bytes>). The byte count isnumel * dtype-bytes(4 forf32/i32, 8 forf64/i64, 1 forbool).non-differentiable operators (N):. The detector's per-node report. See Non-differentiable operators below.
Non-differentiable operators section
Section titled “Non-differentiable operators section”The detector reports operators whose gradient is undefined or
zero-almost-everywhere by their own ONNX semantics: ArgMax / ArgMin
(integer-index output), Floor / Ceil / Round / Sign
(piecewise-constant), the comparison ops Equal / Greater / Less /
GreaterOrEqual / LessOrEqual and the logical ops And / Or / Not /
Xor (Boolean output), Scatter / ScatterElements (replace, not
accumulate), and a Cast whose target is an integer dtype.
A model with one or more of these prints them in graph order. For
tests/fixtures/per_op/reduce/ArgMax.onnx:
non-differentiable operators (1): [ArgMax] ArgMax - integer-index output - zero gradient a.e., undefined on tiesThe NOTE block is always printed, even when the count is 0. It is honest about
the scope: a zero count is not a guarantee that chelis grad will succeed.
Hydronnx gates weighted Gemm AD, but broader operator surfaces still need
explicit AD coverage. See
Limitations and scope.
Errors
Section titled “Errors”chelis-hydronnx-inspect exits non-zero on any parse failure. The error text is
the Display of the underlying HydronnxError (see
Error handling):
$ chelis-hydronnx-inspect /tmp/missing.onnxerror: file not found: /tmp/missing.onnxchelis-hydronnx-emit
Section titled “chelis-hydronnx-emit”Emit a Chelis .ch Surf module from an ONNX model file.
This is the build-time code generator behind the model-load surface. The model
load is sketched as a runtime function, but Chelis is ahead-of-time compiled, so
the actual implementation is this CLI: you run it once per model, ahead of time,
and check the generated .ch in (or regenerate it as part of your build).
Usage: chelis-hydronnx-emit [OPTIONS] <PATH>
Arguments: <PATH> Path to the input .onnx file
Options: -o, --out <OUT> Path to write the generated .ch file. `-` writes to stdout. When omitted, the file is written next to the input with a name derived from the emitted module (e.g. `tabularmlp.ch`)
--module-name <NAME> Override the emitted module-name segment (the part after `Hydronnx.`). Use this when the model's own name trips the chelis `module-pascal-components` lint
--precision <PREC> Target weight precision (`f32` or `f64`). Weights are emitted at their native ONNX dtype; `--precision f64` is inert and prints a loud `warning:` to stderr before proceeding
--batch-dim <N> Override the model's batch dimension. The signature is derived mechanically from the ONNX graph; this flag is inert and prints a loud `warning:` to stderr before proceeding
-h, --help Print help (see a summary with '-h')Behavior
Section titled “Behavior”Output path resolution
Section titled “Output path resolution”-o <path>: write to<path>literally.-o -: write to stdout (useful for piping).- no
-o: write next to the input.onnx, with a filename derived from the emitted module's last component, lowercased, no underscores. A model whose emitted module isHydronnx.IrisClassifierlands atirisclassifier.chnext to the source.onnx. Reef matches the module ladder to file paths literally, so this default keeps the emitted file reef-importable with no further work.
A successful run prints wrote <path> on stdout and exits 0 (or, with -o -,
prints the .ch itself with no wrote ... line).
--module-name
Section titled “--module-name”By default the emitted module is Hydronnx.<PascalCase of model name>. For
example, an ONNX model_name = "iris_classifier" becomes module Hydronnx.IrisClassifier. If the derived name trips the chelis
module-pascal-components lint (for example a single-word lowercase model name
like "resnet50" whose PascalCase form has a digit run), pass --module-name <Name> to override the last segment. The Hydronnx. prefix is fixed.
--precision and --batch-dim: warn-only no-ops
Section titled “--precision and --batch-dim: warn-only no-ops”Both flags are plumbed into EmitOpts but inert. Weights are emitted at the
model's native ONNX dtype, and the batch dimension is read from the ONNX graph
directly. Passing either flag in a way the emit cannot honor prints a loud
warning: to stderr and proceeds (exit 0). The flags exist so the surface
anticipates a later extension; the no-op is honest, not silent.
Passing --precision f64 prints a warning: line to stderr noting that the
flag has no effect and that weights are emitted at their native ONNX dtype, then
proceeds to wrote <path>. Passing --batch-dim N prints a warning: line
noting that the flag has no effect and the model's declared batch dimension is
used as-is, then proceeds. In both cases the exit code is 0.
--precision f32 is not warned on (the native dtype for the fixtures
already is f32, so the flag is honored for that value).
Non-differentiable warning
Section titled “Non-differentiable warning”After parsing, chelis-hydronnx-emit runs the same detector that
chelis-hydronnx-inspect reports, and if the model contains any mathematically
non-differentiable operator, prints a loud warning: block to stderr and
proceeds. The emitted forward is still a valid Chelis function; the warning
is about grad, not about emit.
Example (verified against tests/fixtures/per_op/reduce/ArgMax.onnx):
warning: this model contains 1 mathematically non-differentiable operator(s) - automatic differentiation (`grad`) on the emitted `forward` will not work. chelis's AD does not name the operator; Hydronnx does: [ArgMax] ArgMax - integer-index output - zero gradient a.e., undefined on ties note: removing these operators does not by itself make the model AD-ready; chelis AD support is operator-dependent. Hydronnx gates the weighted Gemm path, but broader models need their own AD parity gate.When the count is 0 the warning is not emitted: chelis-hydronnx-emit does
not echo the inspect CLI's NOTE block. Silence on emit is not a claim of
AD-readiness; see the warning's note: line above and the AD section of
Limitations and scope.
Emitted .ch structure
Section titled “Emitted .ch structure”Every emitted module is a self-contained Chelis file beginning with a provenance
comment block. The shape (taken from
examples/tabular_classifier/src/irisclassifier.ch):
module Hydronnx.IrisClassifierexport (forward)-- hydronnx - generated Chelis module. Do not edit by hand.-- module: Hydronnx.IrisClassifier-- source: examples/tabular_classifier/iris_classifier.onnx-- model: iris_classifier-- opset: 17 (ONNX IR version 8)-- producer: gen_tabular_model.py-- emitted: 2026-05-22T21:28:03Z-- operators (4):-- Gemm x2-- Relu x1-- Softmax x1-- weights (4):-- W1 : f32 [4, 6]-- B1 : f32 [6]-- W2 : f32 [6, 3]-- B2 : f32 [3]def w1() = reshape(to_tensor([cast(0.1523..., f32), ...]), [cast(4, int64), cast(6, int64)])def b1() = to_tensor([cast(-0.0428..., f32), ...])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}Key shape details:
- The module exports exactly one function:
forward. Itssigcarries the ONNX input and output tensor types verbatim: same dims, same dtype. Static ONNX dims become literal integers; an ONNXdim_param(symbolic dim) becomes a Chelis dim variable. Runchelis-hydronnx-emitontests/fixtures/e2e/symbolic_batch.onnxto see this in action; the emittedsigissig forward: tensor[batch, 5, f32] -> tensor[batch, 5, f32]. - Weights are emitted as zero-arg
defs (def w1() = ...). They are baked into the source, not loaded from a file at runtime. Each call site invokes them asw1(). This is the ahead-of-time-friendly form; large weight tensors make this scaling-limited, see Weights and large models. - The emitted body uses chelis Surf builtins (
matmul,relu,softmax,expand,cast, and so on). There is no Hydronnx runtime to depend on.
The emit is deterministic: the same .onnx produces the same .ch
byte-for-byte, modulo the -- emitted: timestamp and -- source: path lines.
The acceptance test (tests/h5_example.rs) pins this.
Errors
Section titled “Errors”Like chelis-hydronnx-inspect, chelis-hydronnx-emit exits non-zero on any
failure, with error: ... on stderr. See Error handling
for the full catalogue. A common example:
$ chelis-hydronnx-emit tests/fixtures/per_op/elementwise/Round.onnx -o -error: unsupported operator 'Round' at node index 0: tests/fixtures/per_op/elementwise/Round.onnxchelis-hydronnx-run
Section titled “chelis-hydronnx-run”Execute an ONNX model with its real weights through the translate and
chelis-ir eval path (Path A of Weights and large models).
translate_model separates structure (a chelis-ir Dag) from weights
(in-memory tensors of any size), and the interpreter runs the DAG with the
weights handed in at eval time, so there is no inline-source weight ceiling.
A real ResNet18 reproduces ONNX Runtime's logits to about 1e-6 this way.
This is the runtime product, not the typed-source product: nothing here is
chelis checked or provable. Verify the architecture with
chelis-hydronnx-emit --weights placeholder; run the numbers here. The
typed-source path with real weights is gated on Chelis typed external constants.
Usage: chelis-hydronnx-run [OPTIONS] <PATH>
Arguments: <PATH> Path to the input .onnx file
Options: --inputs <FILE.npz> Read graph inputs from a .npz file, each array keyed by its raw ONNX graph-input name. Inputs with an initializer default are optional: omit to keep the default, include to override --fill <MODE> Synthesize every graph input that has no initializer default (static input shapes only) [possible values: zeros, ones, deterministic] --weights <FILE.hnw> Override initializer values from a .hnw sidecar weight archive (checksums verified on read) --export-weights <FILE.hnw> Write the model's initializers to a .hnw sidecar weight archive. With no --inputs/--fill, exports and exits without running --json Print full output tensors as one JSON object on stdout instead of the per-output summary -h, --help Print helpExactly one input source is required to run: --inputs (concrete tensors, works
with symbolic batch dims) or --fill (static shapes only; deterministic is
the reproducible (i % 257)/257 - 0.5 pattern the ResNet18 eval gate uses).
--export-weights alone exports the archive and exits (export needs only the
parser, so it also works on models the translator rejects).
A graph input that is also backed by an initializer is the ONNX
default-value pattern, and the initializer is authoritative: --fill never
synthesizes over it, and under --inputs the array is optional. Absent keeps
the default, present overrides it (reported on stderr).
Examples
Section titled “Examples”$ chelis-hydronnx-run model.onnx --inputs input.npzran `tabular_mlp` (25 dag nodes) with real weightsoutput `y`: shape [4, 1] min 0.500076 max 0.500693 mean 0.500384 argmax(flat) 3 first 4 = [0.5000757012052212, ...]
$ chelis-hydronnx-run resnet18.onnx --fill deterministicran `main_graph` (2750 dag nodes) with real weightsoutput `191`: shape [1, 1000] min -3.367538 max 6.307741 mean 0.000010 argmax(flat) 807 ...The --json form prints one object:
{"model": "...", "outputs": [{"name": "...", "shape": [...], "data": [...]}]}
with full f64 tensor values (the interpreter's storage precision; inputs and
weights are promoted to f64 on load; npz dtypes are not cross-checked against
the model's declared input dtypes, and i64 magnitudes above 2^53 lose precision
in the promotion). JSON has no NaN/Infinity literals, so non-finite values are
encoded as the strings "NaN" / "Infinity" / "-Infinity" (with a count
warned on stderr). The default summary reports statistics over the finite values
with the non-finite count alongside.
Sidecar weight archives
Section titled “Sidecar weight archives”--export-weights model.hnw writes every initializer to a
.hnw archive at its native dtype;
--weights model.hnw then runs with the archive's values instead of the
ONNX-embedded ones (every archive tensor must name a model constant and match
its shape; the override count is reported on stderr). The round-trip is
bit-identical and is gated by tests/run_cli.rs. Until Chelis has typed
external constants, this is a validation surface for the archive core: the
emitted .ch cannot reference an archive.
Errors
Section titled “Errors”Exit 1 with error: ... on stderr: parse and translate failures (an unsupported
operator stops translation), a missing npz array (the message lists the arrays
present), a shape that contradicts a statically declared input dim, --fill on
a symbolic input, an archive tensor that names no model initializer or whose
shape disagrees with the model's, a corrupt archive (checksum mismatch), or
--export-weights on a model with a non-finite float initializer (for example a
-inf attention-mask constant; the archive format rejects non-finite floats by
design, naming the tensor and element).