Recursive AI

Run an ONNX model in C#

All onemodel recipes

Load the .onnx file with Model.Load, pass the input as a float[] with its shape, and read the output array. The program below runs the MNIST digit classifier from the ONNX model zoo on a handwritten “7” from the MNIST test set. OneModel is pure managed C#: no ONNX Runtime, no native libraries.

Terminal
dotnet new console -o RunOnnx && cd RunOnnx
dotnet add package RecursiveAI.OneModel --version 0.3.0-preview.1
dotnet add package RecursiveAI.OneImage --version 1.10.4
curl -LO https://github.com/onnx/models/raw/main/validated/vision/classification/mnist/model/mnist-12.onnx
curl -O https://recursiveai.org/onemodel/samples/digit.png

Put this in Program.cs and run dotnet run:

C#
using OneImage;
using RecursiveAI.OneModel;
 
using var model = Model.Load("mnist-12.onnx");
foreach (var i in model.Session.Inputs)
    Console.WriteLine($"Input  {i.Name}: {i.DType} [{string.Join(", ", i.Dims)}]");
foreach (var o in model.Session.Outputs)
    Console.WriteLine($"Output {o.Name}: {o.DType} [{string.Join(", ", o.Dims)}]");
 
// 28 x 28 gray pixels, 0 to 255, white on black: what this model was trained on on
Gray8[] pixels = Image.Load("digit.png").ToArray<Gray8>();
float[] input = pixels.Select(p => (float)p.V).ToArray();
 
float[] scores = model.Run(input, [1, 1, 28, 28]);   // one score per digit, 0 to 9
 
int digit = Array.IndexOf(scores, scores.Max());
Console.WriteLine($"Predicted digit: {digit}");
Console.WriteLine($"Scores: {string.Join(", ", scores.Select(s => $"{s:F0}"))}");
Text
Input  Input3: F32 [1, 1, 28, 28]
Output Plus214_Output_0: F32 [1, 10]
Predicted digit: 7
Scores: -553, 138, 2179, 2320, -3467, -1778, -6442, 8063, -1860, 1034

Model.Load reads the ONNX file and plans it for this CPU; Run takes the values in row-major order and returns the model’s output as a new array. This model returns ten raw scores, one per digit, and the highest wins; ONNX Runtime gives the same ten scores for this image, to the nearest whole number. Load the model once and call Run as often as you like, from any number of threads.

Inputs, shapes and outputs

  • model.Session.Inputs and Outputs give each tensor’s name, element type and dimensions, read from the model. A named dimension such as batch takes any size.
  • Run accepts float[], long[] (token ids) and int[], and an optional shape: leave it out when the model’s input shape is fixed except for its first dimension.
  • A span form, model.Run<float, float>(input, shape, output), writes into your buffer and allocates nothing once warm.
  • For a model with several inputs or outputs, use a Session: see Run an ONNX model with multiple inputs.

Limits

  • OneModel is a preview (0.3.0-preview.1, .NET 10). The API may change before 1.0, and there is no GPU backend: it runs on SIMD CPU kernels.
  • Not every operator is implemented. A model that needs a missing one fails in Model.Load with every missing operator listed: see When an ONNX model won’t load. Coverage is in conformance.
  • Preprocessing is yours. OneModel runs the graph; resizing, normalizing and tokenizing happen in your code, exactly as the model was trained. Here OneImage decodes the PNG; this model wants unscaled 0 to 255 values.
  • Inference only. Training isn’t in this preview.

For bit-identical results on every machine, see Deterministic ONNX inference. Everything else starts in getting started.