# onnx-export-rs
Export inference-only Rust machine-learning model representations to ONNX.
The crate separates canonical weights/tree structures from graph exporters,
so an adapter for a training library does not need to implement ONNX itself.
The default export path has a minimum supported Rust version of **1.75** and
avoids runtime-specific native dependencies. Enabling the optional `validate`
feature raises the requirement to **1.91**, determined by its Tract 0.23
validation dependency; the `onnxruntime`, `smartcore`, and `linfa` features
likewise pull heavier toolchains.
> Status: the canonical API, exporters, Tract validation, and adapters for
> `smartcore` linear/logistic/ridge/Lasso/Elastic Net and `linfa` linear/binary
> logistic/multiclass logistic and Linfa classification trees are implemented.
> SmartCore keeps fitted SVM, tree, forest, and XGBoost internals private. The
> opt-in `smartcore-compat` feature provides adapters pinned to SmartCore 0.5.5
> by reading its serialized representation. Layout changes are rejected as
> errors, and SVM adapters require the fitting kernel to be supplied explicitly.
> The same compatibility layer now covers Euclidean k-nearest neighbors,
> k-means, Gaussian Naive Bayes, and Extra Trees. Public SmartCore adapters
> cover PCA and truncated SVD transforms. A parallel opt-in `linfa-compat`
> feature reads Linfa 0.8.1's serialized state to cover its Tweedie GLM, PLS
> regression, Gaussian and Multinomial Naive Bayes, and nonlinear-kernel SVM
> regression and binary classification.
## Format compatibility
Models use **ONNX IR 8**, core **opset 13**, and (for trees)
**`ai.onnx.ml` opset 3**. Protobuf bindings are a small `prost`-based subset of
the stable ONNX wire schema, checked into the crate as Rust rather than built
with `protoc`. This keeps builds reproducible and avoids coupling to private
`tract-onnx` bindings.
Exported numeric parameters are narrowed from `f64` to `f32`. This maximizes
runtime compatibility but can lose precision for extreme values. Inputs use a
symbolic `batch` first dimension.
| Linear / Ridge / Lasso / ElasticNet | `Gemm` | 13 | `[batch, 1]` |
| Binary logistic | `Gemm`, `Sigmoid` | 13 | positive-class probability |
| Multiclass logistic | `Gemm`, `Softmax(axis=1)` | 13 | class probabilities |
| Decision tree / random forest regression | `TreeEnsembleRegressor` | ML 3 | target scores |
| Decision tree / random forest classification | `TreeEnsembleRegressor`, `ArgMax` | ML 3 / core 13 | zero-based class index |
| SVM regression / one-class SVM | `SVMRegressor` | ML 1 | score |
| SVM classification | `SVMClassifier` | ML 1 | integer label and class scores |
| Gradient-boosted trees | `TreeEnsembleRegressor` | ML 3 | target, probability, or class index |
| PCA / truncated SVD / affine transforms | `Gemm` | 13 | transformed features |
| k-means / nearest centroid | arithmetic reductions, `ArgMin` | 13 | zero-based cluster index |
| k-nearest neighbors | arithmetic, `TopK`, `Gather`, voting | 13 | target or integer label |
| Gaussian Naive Bayes | arithmetic reductions, `ArgMax`, `Gather` | 13 | integer label |
| Multinomial / Bernoulli Naive Bayes | `Gemm`, `ArgMax`, `Gather` | 13 | integer label |
| Categorical Naive Bayes | `Gather`, score accumulation, `ArgMax` | 13 | integer label |
| DBSCAN radius prediction | distance reduction, vote matrix, `ArgMax` | 13 | cluster label / noise |
Linear and logistic graphs are round-trip tested with Tract 0.23.4. Tract can
parse ONNX-ML tree ensembles but currently reports its typed translator as
unimplemented, so use ONNX Runtime or another ONNX-ML-capable runtime to execute
tree, SVM, and gradient-boosting exports. Enable `onnxruntime` to run the
Microsoft ONNX Runtime-backed integration tests for these operators.
## Example
```rust
use ndarray::array;
use onnx_export_rs::{canonical::LinearModelWeights, exporters::export_linear, save_to_file};
let weights = LinearModelWeights::new(array![2.0, -1.0], 0.5);
let model = export_linear(&weights);
save_to_file(&model, "linear.onnx")?;
# Ok::<(), onnx_export_rs::Error>(())
```
Binary logistic models use one coefficient row and `n_classes = 2`.
Multiclass models use one row per class. Tree adapters can recursively build
`RecursiveNode` values and pass them through `flatten_tree`.
Enable `validate` for Tract-backed `validate_export` and `compare_predictions`.
Enable `smartcore` or `linfa` for the corresponding fitted-model adapters.
Smartcore logistic adapters also expose the ordered class labels associated
with the exported probability columns. Enable `smartcore-compat` for
`decision_tree_regressor`, `decision_tree_classifier`,
`random_forest_regressor`, `random_forest_classifier`, `xgboost_regressor`,
`svm_regressor`, binary `svm_classifier`, `extra_trees_regressor`,
`kmeans`, Euclidean `knn_regressor`/`knn_classifier`, and
all four Naive Bayes variants, `dbscan`, and `standard_scaler` in
`adapters::smartcore_compat`. The classifier tree/forest adapters return class
labels alongside their canonical forest because tree classification exports
produce zero-based class indices.
Enable `linfa-compat` for `tweedie_regressor` (generalized linear models),
`pls`, `gaussian_naive_bayes`, `multinomial_naive_bayes`, and nonlinear-kernel
`svm_regressor`/binary `svm_classifier` in `adapters::linfa_compat`, pinned to
Linfa 0.8.1. Unlike the SmartCore SVM adapters, these recover the kernel
(Gaussian or polynomial) from the serialized state, so no kernel argument is
required; linear-kernel SVMs store no support vectors, so use the public
`adapters::linfa::linear_svm_score` for those. The binary `svm_classifier`
takes the integer labels to emit for Linfa's positive (`true`) and negative
(`false`) classes, which its boolean targets do not otherwise carry.
## Scope
- K-means and Gaussian Naive Bayes use portable core-ONNX subgraphs because
ONNX-ML has no dedicated operators for them. Agglomerative clustering has no
out-of-sample prediction in SmartCore and therefore has no inference graph.
- Agglomerative clustering, Linfa's batch DBSCAN/OPTICS outputs, and other
training-only algorithms do not expose a fitted out-of-sample inference
model and therefore cannot produce a reusable ONNX inference graph.
- Gradient boosting is supported through the canonical tree representation;
[`perpetual`](https://crates.io/crates/perpetual) remains preferable when its
native exporter already covers the fitted model.
Licensed under MIT.