04 · deploy

Past where
scikit-learn stops.

Export to one portable ONNX artifact, serve a drift-monitored endpoint, version every model with its lineage, and — the framework pointed at itself — let AutoML search for the best deployable pipeline.

Portability

ONNX in and out.

With onnx, any model — or a whole pipeline — exports to one .onnx file. Whole-pipeline export folds leading affine scalers into the estimator's graph: raw features in, predictions out. InferenceModel::load runs any ONNX file back through tract.

let mut pipe = Pipeline::new()
    .step("scale", StandardScaler::new())
    .estimator("lr", LinearRegression::new());
pipe.fit(&train)?;
let native = pipe.predict(&probe)?;

pipe.export_onnx("pipeline.onnx")?;                 // scaler + model, one graph
let model = InferenceModel::load("pipeline.onnx")?;
let via_onnx = model.predict(&probe)?;              // matches `native`

Linear/affine/pipeline graphs run inside tract for a full round-trip. A random forest exports to a valid ONNX-ML tree-ensemble artifact for external runtimes (onnxruntime); tract implements NN ops, not the ONNX-ML tree ops.

cargo run --example portability --features "smartcore-backend onnx"

Operations

Registry, drift, serving.

The registry versions the ONNX artifact (content-addressed, with a reference distribution and movable tags); monitor watches the prediction stream for PSI drift; serve exposes a validated endpoint that feeds the monitor.

let reg = Registry::local("./models");
let v1 = reg.register("demand", &model, Metadata {
    metrics: vec![("r2".into(), 1.0)],
    reference: reference.clone(),   // the distribution drift watches against
    note: "baseline".into(),
})?;
reg.tag("demand", &v1.id, "prod")?;
let reverted = reg.rollback("demand", "prod")?;   // revert in one line

// serve the prod artifact, watching for drift on every request
Server::from_onnx(reg.onnx_path("demand", "prod")?)?
    .route("/predict")
    .with_monitor(DriftMonitor::psi(&reference)?)
    .serve("0.0.0.0:8080").await?;         // POST /predict, GET /metrics

cargo run --example operations --features "onnx registry monitor serve"

Serving any model. The Server runs linear / NN ONNX graphs through tract, and evaluates ONNX-ML tree ensembles (a forest) with a small native interpreter — so a model exported by Millwright always serves in Millwright, and the artifact stays portable to any ONNX runtime.
Specialized shapes

Time series & out-of-core.

Same contract, different data shapes — each gets its own trait. These two crates pin ndarray 0.15 while the rest of the stack uses 0.16; Cargo links both and converts only inside the adapters.

// time series (feature = "timeseries")
let mut arima = AutoArima::new().max_p(3).max_q(3);
arima.fit(&series)?;                  // &[f64]
let forecast = arima.forecast(6)?;   // six steps ahead

// out-of-core (feature = "incremental") — never holds the whole set in memory
let mut model = IncrementalLinear::with_rate(0.05, 0.0);
for batch in batches {
    model.partial_fit(&batch)?;      // one batch at a time
}

cargo run --example specialized --features "timeseries incremental"

Synthesis

AutoML — the framework, pointed at itself.

Profiling, preprocessing, CV, search, and ensembling are exactly what an AutoML engine needs — so AutoML is not a bolt-on, it is the framework orchestrating its own parts. Point it at data and a budget; get a leaderboard and the best deployable model.

let result = AutoML::classifier()      // or ::regressor()
    .budget(Budget::trials(20))         // or Budget::minutes(10)
    .metric(Metric::F1)
    .cv(StratifiedKFold::new(5))
    .seed(0)
    .fit(&train)?;

println!("{}", result.leaderboard());
result.export_onnx("model.onnx")?;   // deployable — unlike a TPOT object

cargo run --example automl --features "automl onnx"