ONNX Usage Patterns¶
This page maps common ONNX use cases to the Uni-Xervo developer flow.
Tabular regression or classification¶
Best fit for local/onnx.
Developer flow:
- Build numeric feature tensors in app code.
- Put them into
TensorBatch. - Call
run(). - Read scalar or logits outputs.
Why it works well:
- no tokenizer,
- usually one or two tensors,
- minimal postprocessing.
Sequence classification¶
Examples:
- sentiment
- topic classification
- moderation
Developer flow:
- Tokenize text in app code.
- Build
input_idsandattention_mask. - Call
run(). - Read logits and map labels.
This is one of the best HF ONNX patterns for Uni-Xervo today.
Token classification / NER¶
Examples:
- NER
- PII tagging
- slot extraction
Developer flow:
- Tokenize with offsets.
- Build transformer input tensors.
- Call
run(). - Read token logits.
- Reconstruct spans in app code.
Uni-Xervo handles execution cleanly here, but the token-to-span logic still belongs in your application.
Dense text embedding (Embed task)¶
As of 0.8.0, local/onnx serves the Embed task directly — this replaces the retired local/fastembed provider. The popular text-embedding aliases (BGESmallENV15, AllMiniLML6V2, NomicEmbedTextV15, MultilingualE5Base, …) ship as built-in presets that resolve the HF repo, ONNX path, pooling kind, dimensions, and token_type_ids automatically. For multilingual dense embeddings, BGEM3 (official BAAI/bge-m3) and BGEM3Dense (the dense head of the multi-output aapot/bge-m3-onnx export) are both available.
Catalog config (preset alias):
{
"alias": "embed/local",
"task": "embed",
"provider_id": "local/onnx",
"model_id": "BGESmallENV15"
}
Catalog config (custom HF model, pass-through):
{
"alias": "embed/custom",
"task": "embed",
"provider_id": "local/onnx",
"model_id": "Snowflake/snowflake-arctic-embed-m",
"options": {
"artifact": "onnx/model.onnx",
"pooling": "cls",
"dimensions": 768,
"token_type_ids": true
}
}
Developer flow:
- Resolve the typed handle:
let embedder = runtime.embedding("embed/local").await?; - Call
embedder.embed(&["hello world", "second doc"]).await?— Uni-Xervo handles tokenization, ORT session execution, pooling, and L2 normalization. - Each row of
result.vectorsisVec<f32>of lengthembedder.dimensions().
If you have a custom export that returns hidden states and you want to handle pooling yourself, use the Raw task instead and pool in app code.
Learned-sparse embedding (EmbedSparse task)¶
Produces term-weight (lexical) vectors for SPLADE-style or BGE-M3 sparse retrieval. sparse_method selects the post-processing recipe: "mlm" reads MLM logits over the full vocabulary (SPLADE term expansion), "lexical" re-weights the input token ids (BGE-M3 lexical weights).
Catalog config:
{
"alias": "sparse/splade",
"task": "embed_sparse",
"provider_id": "local/onnx",
"model_id": "naver/splade-v3",
"options": {
"sparse_method": "mlm",
"max_seq_len": 512
}
}
Developer flow:
- Resolve the typed handle:
let embedder = runtime.sparse_embedder("sparse/splade").await?; - Call
embedder.embed(&["hello world", "second doc"]).await?. - Each row of
result.vectorsis aSparseVector— aVec<(u32, f32)>of(term_id, weight)pairs; useoptions.top_kto cap the number of retained terms.
Multi-vector / late-interaction embedding (EmbedMultiVector task)¶
Produces per-token ColBERT-style embeddings for late-interaction (MaxSim) retrieval.
Catalog config:
{
"alias": "colbert/v2",
"task": "embed_multi_vector",
"provider_id": "local/onnx",
"model_id": "colbert-ir/colbertv2.0",
"options": {
"dimensions": 128,
"normalize": true,
"drop_special_tokens": true
}
}
Developer flow:
- Resolve the typed handle:
let embedder = runtime.multi_vector_embedder("colbert/v2").await?; - Call
embedder.embed(&["hello world", "second doc"]).await?. - Each row of
result.vectorsis aVec<Vec<f32>>— one dense vector per surviving token.
Single-pass hybrid embedding (EmbedHybrid task)¶
For multi-output graphs that fuse several heads — notably BGE-M3
(aapot/bge-m3-onnx, which emits dense_vecs / sparse_vecs / colbert_vecs)
— the hybrid task runs one forward pass and post-processes every requested
head from it, instead of loading three sessions for the per-task Embed /
EmbedSparse / EmbedMultiVector resolvers.
Catalog config (preset alias):
{
"alias": "embed_hybrid/bgem3",
"task": "embed_hybrid",
"provider_id": "local/onnx",
"model_id": "BGEM3Hybrid"
}
Developer flow:
- Resolve the handle:
let model = runtime.hybrid_embedder("embed_hybrid/bgem3").await?; - Call
model.embed(&["query", "doc"], HeadSet::ALL).await?— a single pass. result.dense/result.sparse/result.multi_vectorare eachSomefor the heads you requested via theHeadSet(and the graph exposes),Noneotherwise.model.available_heads()reports what the graph supports.
This is preset-driven: only models with a hybrid preset (declaring each head's output and recipe) resolve here. Single-head models use the per-task resolvers.
Cross-encoder reranking (Rerank task)¶
Catalog config:
{
"alias": "rerank/cross",
"task": "rerank",
"provider_id": "local/onnx",
"model_id": "cross-encoder/ms-marco-MiniLM-L6-v2"
}
Developer flow:
- Resolve the handle:
let reranker = runtime.reranker("rerank/cross").await?; - Call
reranker.rerank("query", &["doc a", "doc b"]).await?to get scored documents back.
Image classification¶
Developer flow:
- Resize/normalize image in app code.
- Build the expected image tensor layout.
- Call
run(). - Decode logits.
The runtime is a good fit; preprocessing remains model-specific.
Object detection or complex multimodal graphs¶
These are possible, but more manual.
Developer flow:
- Inspect signatures carefully.
- Build custom tensors.
- Run inference.
- Decode boxes, masks, spans, or custom outputs outside Uni-Xervo.
That is the point where local/onnx intentionally stays low-level.
Which ONNX cases feel easiest today¶
Best current DX:
- tabular models
- regression
- sequence classification
- NER
- custom numeric graphs
More manual but still supported:
- image models
- object detection
- audio models
- unusual multi-input graphs