ONNX
Overview
The smile.onnx module provides an idiomatic Java API for running ONNX models with the ONNX Runtime inference engine. It wraps the ORT C API through Panama FFM (smile.onnx.foreign); applications use only the high-level classes described here.
Shared tensor / deep-learning APIs live on Deep Learning. HTTP serving of .onnx models is covered on SMILE Serve.
┌──────────────────────────────────────┐
│ Your Application │
├──────────────────────────────────────┤
│ smile.onnx │
│ InferenceSession OrtValue ... │
├──────────────────────────────────────┤
│ smile.onnx.foreign (Panama FFM) │
├──────────────────────────────────────┤
│ onnxruntime shared library │
└──────────────────────────────────────┘
Prerequisites
| Requirement | Minimum |
|---|---|
| Java | 22+ (Panama FFM; SMILE targets Java 25) |
| ONNX Runtime native library | 1.18+ (API version 22) |
| JVM flag | --enable-native-access=ALL-UNNAMED |
Download pre-built packages from the ORT releases page and place libonnxruntime.so / libonnxruntime.dylib / onnxruntime.dll on the OS library search path:
# Linux
export LD_LIBRARY_PATH=/path/to/onnxruntime/lib:$LD_LIBRARY_PATH
# macOS
export DYLD_LIBRARY_PATH=/path/to/onnxruntime/lib:$DYLD_LIBRARY_PATH
# Windows (PowerShell)
$env:PATH = "C:\path\to\onnxruntime\lib;" + $env:PATH
java --enable-native-access=ALL-UNNAMED -jar myapp.jar
Package Structure
| Class / enum | Role |
|---|---|
InferenceSession | Loads a model and runs inference |
OrtValue | Tensor container (input and output) |
SessionOptions | Configures a session before loading |
RunOptions | Configures a single inference run |
Environment | Shared OrtEnv (thread pools, logging) |
ModelMetadata | Producer, version, custom metadata |
NodeInfo / TensorInfo | Name, type, and shape for a node |
ElementType / OnnxType | Tensor element types and value kinds |
GraphOptimizationLevel / ExecutionMode | Optimization and execution mode enums |
LoggingLevel / OnnxException | ORT logging severity and errors |
Quick Start
import smile.onnx.*;
import java.util.Map;
// 1. Load the model
try (var session = InferenceSession.create("resnet50.onnx")) {
// 2. Inspect inputs
session.inputNames().forEach(System.out::println);
// → "data"
// 3. Build an input tensor (batch=1, channels=3, height=224, width=224)
float[] pixels = preprocessImage(...); // your image preprocessing
long[] shape = { 1, 3, 224, 224 };
try (OrtValue input = OrtValue.fromFloatArray(pixels, shape)) {
// 4. Run inference
OrtValue[] outputs = session.run(Map.of("data", input));
// 5. Read the result
float[] scores = outputs[0].toFloatArray();
int classId = argmax(scores);
System.out.println("Predicted class: " + classId);
// 6. Release output tensors
for (OrtValue v : outputs) v.close();
}
}
InferenceSession
InferenceSession loads an ONNX model, optimizes its graph, and executes inference. It implements AutoCloseable — always use try-with-resources or call close(). run is thread-safe; multiple threads may call it concurrently on the same session.
import smile.onnx.*;
import java.nio.file.Files;
import java.nio.file.Path;
// From a file path (default options)
InferenceSession session = InferenceSession.create("model.onnx");
// From a file path with custom options
try (var opts = new SessionOptions()) {
opts.setIntraOpNumThreads(4)
.setGraphOptimizationLevel(GraphOptimizationLevel.ENABLE_ALL);
session = InferenceSession.create("model.onnx", opts);
}
// From a byte array (database / JAR resource)
byte[] modelBytes = Files.readAllBytes(Path.of("model.onnx"));
session = InferenceSession.create(modelBytes);
// Run all outputs (most common)
OrtValue[] outputs = session.run(inputs);
// Selected outputs + per-call options
try (var runOpts = new RunOptions()) {
runOpts.setLogTag("my-run");
outputs = session.run(inputs, new String[]{"output_0"}, runOpts);
}
OrtValue — Tensors
OrtValue wraps an ORT tensor with Java-friendly factories and extractors. It implements AutoCloseable. Shape is a long[]; -1 means a dynamic dimension.
import smile.onnx.OrtValue;
import smile.onnx.TensorInfo;
import smile.onnx.ElementType;
import smile.onnx.OnnxType;
import java.util.Arrays;
// Create from Java arrays
OrtValue v = OrtValue.fromFloatArray(
new float[]{1f, 2f, 3f, 4f}, new long[]{2, 2});
OrtValue d = OrtValue.fromDoubleArray(new double[]{1.0, 2.0}, new long[]{2});
OrtValue i = OrtValue.fromIntArray(new int[]{10, 20}, new long[]{2});
OrtValue l = OrtValue.fromLongArray(new long[]{100L, 200L}, new long[]{2});
// Extract / inspect outputs
TensorInfo ti = outputs[0].tensorInfo();
System.out.println(ti.elementType()); // e.g. FLOAT
System.out.println(Arrays.toString(ti.shape())); // e.g. [1, 1000]
float[] scores = outputs[0].toFloatArray();
OnnxType kind = outputs[0].onnxType(); // TENSOR, SEQUENCE, MAP, …
boolean isDynamic = ti.isDynamic();
SessionOptions
SessionOptions is a fluent builder for session-level configuration. Close it after the session is created.
import smile.onnx.*;
try (var opts = new SessionOptions()) {
// Thread counts (0 = let ORT decide)
opts.setIntraOpNumThreads(4) // within one operator
.setInterOpNumThreads(2); // across independent ops
opts.setGraphOptimizationLevel(GraphOptimizationLevel.ENABLE_ALL);
opts.setOptimizedModelFilePath("resnet50_opt.onnx");
opts.setExecutionMode(ExecutionMode.SEQUENTIAL); // default
// opts.setExecutionMode(ExecutionMode.PARALLEL);
opts.enableCpuMemArena();
opts.setLogId("my-session");
opts.setLogSeverityLevel(LoggingLevel.WARNING);
opts.appendCudaExecutionProvider(0);
opts.addConfigEntry("session.disable_prepacking", "1");
try (var session = InferenceSession.create("model.onnx", opts)) {
// ...
}
}
| GraphOptimizationLevel | Description |
|---|---|
DISABLE_ALL | No optimizations; useful for debugging |
ENABLE_BASIC | Constant folding, redundant node elimination |
ENABLE_EXTENDED | Complex operator fusions |
ENABLE_LAYOUT | Memory layout transformations |
ENABLE_ALL | All optimizations (recommended for production) |
| ExecutionMode | When to use |
|---|---|
SEQUENTIAL | Low-latency single-request serving (default) |
PARALLEL | High-throughput batch workloads with parallelizable subgraphs |
RunOptions
RunOptions configures a single session.run() call. setTerminate() signals ORT to abort the current run (useful for request timeouts).
import smile.onnx.*;
import java.util.concurrent.*;
try (var runOpts = new RunOptions()) {
runOpts.setLogTag("request-42")
.setLogSeverityLevel(LoggingLevel.WARNING);
OrtValue[] outputs = session.run(inputs, outputNames, runOpts);
}
// Cancel from another thread
var runOpts = new RunOptions();
scheduler.schedule(() -> runOpts.setTerminate(), 5, TimeUnit.SECONDS);
try {
OrtValue[] outputs = session.run(inputs, outputNames, runOpts);
} catch (OnnxException ex) {
System.out.println("Run was cancelled: " + ex.getMessage());
} finally {
runOpts.close();
}
Environment
By default each InferenceSession.create(…) creates a private OrtEnv. When you have multiple sessions in the same process, share one Environment to reduce thread-pool overhead.
import smile.onnx.*;
import java.util.List;
try (var env = new Environment(LoggingLevel.WARNING, "my-app")) {
try (var s1 = env.createSession("model_a.onnx");
var s2 = env.createSession("model_b.onnx");
var s3 = env.createSession("model_c.onnx")) {
// All sessions share the same OrtEnv thread pools
}
}
System.out.println(Environment.buildInfo());
List<String> providers = Environment.availableProviders();
// e.g. ["CPUExecutionProvider", "CUDAExecutionProvider"]
env.setLoggingLevel(LoggingLevel.VERBOSE);
Model Introspection
import smile.onnx.*;
import java.util.Arrays;
try (var session = InferenceSession.create("model.onnx")) {
System.out.println("Inputs (" + session.inputCount() + "):");
for (NodeInfo ni : session.inputInfos()) {
System.out.println(" " + ni.name() + " : " + ni.onnxType());
if (ni.isTensor()) {
TensorInfo ti = ni.tensorInfo();
System.out.println(" element type : " + ti.elementType());
System.out.println(" shape : " + Arrays.toString(ti.shape()));
System.out.println(" dynamic? : " + ti.isDynamic());
}
}
ModelMetadata meta = session.metadata();
System.out.println("Producer : " + meta.producerName());
System.out.println("Graph : " + meta.graphName());
System.out.println("Version : " + meta.version());
System.out.println("Custom : " + meta.customMetadata());
}
Inputs (1):
data : TENSOR
element type : FLOAT
shape : [-1, 3, 224, 224]
dynamic? : true
Data Types
| ElementType | Java | Notes |
|---|---|---|
FLOAT / DOUBLE | float / double | IEEE 754 |
INT8 … INT64 | byte … long | Signed integers |
UINT8 … UINT64 | same widths | Use unsigned helpers in Java |
BOOL | byte | Stored as 0 / 1 |
STRING | String | UTF-8 |
FLOAT16 / BFLOAT16 / … | — | No direct Java primitive mapping |
OnnxType describes the kind of an OrtValue: TENSOR, SEQUENCE, MAP, SPARSE_TENSOR, OPTIONAL, OPAQUE.
Execution Providers
Execution providers offload work to GPUs or accelerators. Configure them on SessionOptions; ORT tries them in registration order and falls back to CPU. Register GPU providers before relying on CPU (CPU is always available).
import smile.onnx.*;
import java.util.Map;
try (var opts = new SessionOptions()) {
opts.appendCudaExecutionProvider(0);
opts.appendTensorRTExecutionProvider(0);
opts.appendRocmExecutionProvider(0);
opts.appendDirectMLExecutionProvider(0); // Windows GPU
opts.appendCoreMLExecutionProvider(Map.of(
"ModelFormat", "MLProgram",
"MLComputeUnits", "ALL"));
opts.appendVitisAiExecutionProvider(Map.of(
"cache_dir", "C:\\temp\\vaip_cache",
"log_level", "info"));
try (var session = InferenceSession.create("model.onnx", opts)) {
// Runs on the first available registered provider
}
}
System.out.println(Environment.availableProviders());
Errors, Resources & Logging
ORT errors surface as unchecked OnnxException with an ORT error code. Always close InferenceSession, OrtValue, SessionOptions, RunOptions, and Environment (try-with-resources). Session logging uses SessionOptions.setLogSeverityLevel; process-wide logging uses Environment / LoggingLevel.
Performance Tuning
| Concern | Recommendation |
|---|---|
| First-load latency | ENABLE_ALL + setOptimizedModelFilePath |
| Throughput | ExecutionMode.PARALLEL + setInterOpNumThreads |
| Latency | SEQUENTIAL (default) + setIntraOpNumThreads |
| Many models | Share one Environment |
| Memory | disableCpuMemArena() / disableMemPattern() |
| Benchmarking | enableProfiling("profile_run") → Chrome trace |
Tutorials
Image Classification with ResNet-50
import smile.onnx.*;
import java.util.Map;
float[] pixels = preprocess("cat.jpg", 224, 224); // NCHW float[1*3*224*224]
long[] shape = {1, 3, 224, 224};
try (var session = InferenceSession.create("resnet50.onnx");
OrtValue input = OrtValue.fromFloatArray(pixels, shape)) {
System.out.println("Input : " + session.inputInfos().getFirst());
System.out.println("Output : " + session.outputInfos().getFirst());
String inputName = session.inputNames().getFirst();
OrtValue[] outputs = session.run(Map.of(inputName, input));
try {
float[] logits = outputs[0].toFloatArray();
int classId = argmax(softmax(logits));
System.out.printf("Predicted class: %d%n", classId);
} finally {
for (OrtValue v : outputs) v.close();
}
}
NLP with a BERT Tokenizer Model
Many NLP models take INT64 input IDs. Pass tokenized text as long tensors:
import smile.onnx.*;
import java.util.*;
try (var session = InferenceSession.create("bert-base-uncased.onnx")) {
for (NodeInfo ni : session.inputInfos()) {
System.out.println(ni.name() + " -> " + ni.tensorInfo());
}
int batchSize = 1, seqLen = 128;
long[] inputIds = new long[batchSize * seqLen];
long[] attentionMask = new long[batchSize * seqLen];
long[] tokenTypeIds = new long[batchSize * seqLen];
Arrays.fill(attentionMask, 1L);
long[] shape = {batchSize, seqLen};
try (OrtValue ids = OrtValue.fromLongArray(inputIds, shape);
OrtValue mask = OrtValue.fromLongArray(attentionMask, shape);
OrtValue types = OrtValue.fromLongArray(tokenTypeIds, shape)) {
Map<String, OrtValue> inputs = Map.of(
"input_ids", ids,
"attention_mask", mask,
"token_type_ids", types);
OrtValue[] outputs = session.run(inputs);
try {
float[] hidden = outputs[0].toFloatArray();
System.out.println("Hidden state size: " + hidden.length);
} finally {
for (OrtValue v : outputs) v.close();
}
}
}
Sharing an Environment across Sessions
import smile.onnx.*;
try (var env = new Environment(LoggingLevel.WARNING, "model-server")) {
System.out.println("ORT build : " + Environment.buildInfo());
System.out.println("Providers : " + Environment.availableProviders());
try (var opts = new SessionOptions()) {
opts.setGraphOptimizationLevel(GraphOptimizationLevel.ENABLE_ALL)
.setIntraOpNumThreads(4);
try (InferenceSession detector = env.createSession("detector.onnx", opts);
InferenceSession classifier = env.createSession("classifier.onnx", opts)) {
// Both sessions share the OrtEnv thread pools
}
}
}
GPU Inference with CUDA
import smile.onnx.*;
try (var opts = new SessionOptions()) {
opts.appendCudaExecutionProvider(0)
.setGraphOptimizationLevel(GraphOptimizationLevel.ENABLE_ALL);
try (var session = InferenceSession.create("model.onnx", opts)) {
// Prefer CUDA when the provider is compiled into ORT
}
}
Additional recipes in the module guide cover loading a model from a JAR resource, enabling Chrome-trace profiling with enableProfiling, and cancelling long-running inference via RunOptions.setTerminate().
Serving
SMILE Serve loads .onnx files from smile.onnx.model and exposes /api/v1/onnx/{id}. See SMILE Serve — ONNX Inference.
See Also
- Deep Learning — LibTorch, layers, training; overview of
smile.onnxin the module map - Computer Vision — EfficientNet / ImageNet pipelines (native LibTorch path)
- ONNX GenAI — generative AI token loop and
GenAiChatModel - SMILE Serve — production HTTP API for ONNX models