ONNX Runtime Guide¶
local/onnx is Uni-Xervo's raw tensor runtime for ONNX models. This guide
covers the raw task. The same provider also serves higher-level tasks —
embed, rerank, embed_sparse,
embed_multi_vector,
embed_hybrid,
embed_image, nlp, and ocr — which interpret outputs
for you; see the
task trait surface for the full list.
Use it when you want Uni-Xervo to handle:
- alias-based model resolution,
- Hugging Face snapshot download and caching,
- ONNX Runtime session loading,
- tensor signature introspection,
- batch validation and splitting,
- timeout/retry/warmup wrappers.
Your application still owns preprocessing and postprocessing.
What local/onnx is for¶
local/onnx is best for:
- custom numeric models,
- tabular regression/classification,
- HF transformer exports where you already control tokenization,
- NER / classification / embedding pipelines that need raw tensor access,
- any ONNX graph where app code should interpret outputs.
It is not a high-level task wrapper like Python transformers.
Catalog shape¶
Use:
provider_id: "local/onnx"task: "raw"
Example:
{
"alias": "raw/minilm",
"task": "raw",
"provider_id": "local/onnx",
"model_id": "nixiesearch/all-MiniLM-L6-v2-onnx",
"options": {
"artifact": "model.onnx"
}
}
Local path vs Hugging Face repo¶
model_id can be either:
- a local filesystem path to an ONNX file, or
- a Hugging Face repo ID
For HF-backed aliases, Uni-Xervo snapshots the full repo revision into cache before loading the selected artifact. That means repo-local sidecars such as tokenizer files, config files, and external data files are available next to the ONNX model on disk.
If the repo contains multiple .onnx files, set options.artifact.
Runtime API¶
Resolve raw ONNX handles with runtime.raw_tensor_model(alias).
use ndarray::{arr2, ArrayD};
use uni_xervo::runtime::ModelRuntime;
use uni_xervo::traits::{TensorBatch, TensorValue};
let runner = runtime.raw_tensor_model("raw/minilm").await?;
let mut batch = TensorBatch::new();
batch.insert(
"input_ids".to_string(),
TensorValue::I64(arr2(&[[101_i64, 7592, 2088, 102]]).into_dyn()),
);
batch.insert(
"attention_mask".to_string(),
TensorValue::I64(arr2(&[[1_i64, 1, 1, 1]]).into_dyn()),
);
let outputs = runner.run(&batch).await?;
let logits = outputs.get("last_hidden_state").unwrap();
Useful APIs:
runtime.raw_tensor_model(alias)runner.input_signature()runner.output_signature()runner.max_batch_size()runner.run(&batch)runner.run_batch(&batches)
Tensor contract¶
Uni-Xervo does not interpret model semantics. It only validates names, dtypes, and shapes against the ONNX graph signature.
Core types:
TensorBatch: ordered tensor map keyed by input/output nameTensorValue: rawndarray::ArrayD<T>over supported ONNX tensor dtypesTensorSpec: name, dtype, and dimension metadata
Supported TensorValue variants cover:
f32f64i32i64boolstring
Provider options¶
local/onnx accepts these options keys:
artifactmax_batch_sizeexecution_providersgraph_optimization_levelinter_op_num_threadsintra_op_num_threads
Example:
{
"artifact": "model.onnx",
"max_batch_size": 32,
"execution_providers": ["cuda", "cpu"],
"graph_optimization_level": "extended",
"intra_op_num_threads": 4
}
Execution providers¶
execution_providers is optional.
Defaults:
- CPU-only builds:
["cpu"] gpu-cudabuilds: ORT-backed providers prefer["cuda", "cpu"]gpu-metalbuilds: ORT-backed providers prefer["coreml", "cpu"]
Supported names:
cpucudacoreml
Vendor names available under provider-onnx-dynamic: rocm, directml,
openvino, qnn, tensorrt, webgpu (each requires a matching ORT library at
ORT_DYLIB_PATH).
Mixing GPU and CPU placement in one catalog¶
execution_providers is set per ModelAliasSpec, so a single
ModelRuntime can run some models on GPU and others on CPU even when
the binary was built with gpu-cuda (or gpu-metal). The placement
choice consumes the build's GPU capability — it does not require a
different binary.
ModelRuntime::builder()
.register_provider(LocalOnnxProvider::new())
.catalog(vec![
// Cheap embedder: keep on CPU to leave VRAM for the reranker.
ModelAliasSpec {
alias: "embed/bge-small".into(),
task: ModelTask::Embed,
provider_id: "local/onnx".into(),
model_id: "BGESmallENV15".into(),
options: serde_json::json!({"execution_providers": ["cpu"]}),
// …
},
// Heavy reranker: run on GPU with CPU as ORT-internal fallback.
ModelAliasSpec {
alias: "rerank/bge-large".into(),
task: ModelTask::Rerank,
provider_id: "local/onnx".into(),
model_id: "BAAI/bge-reranker-base".into(),
options: serde_json::json!({
"execution_providers": ["cuda", "cpu"],
}),
// …
},
])
.build()
.await?;
The order in execution_providers is priority; ORT silently falls
through per-op when an EP can't run a particular kernel.
For local/mistralrs models the equivalent per-spec control is
force_cpu: true — see the
mistralrs device placement reference.
The two mechanisms can be combined in a single catalog (an ONNX
embedder on CPU + a mistralrs generator on GPU) without conflict.
On a build with neither gpu-cuda nor gpu-metal, both
execution_providers and force_cpu are no-ops — the only available
device is CPU.
Developer workflow¶
Typical flow for HF transformer exports:
- Configure
local/onnxin the catalog. - Resolve
runtime.raw_tensor_model(alias). - Load
tokenizer.json/config.jsonfrom the same cached snapshot if needed. - Build
TensorBatchinputs in app code. - Run inference.
- Interpret outputs in app code.
See the live test coverage in tests/local_onnx_hf_e2e_test.rs for real classification and NER examples.