Transmuter ⚗️

Transmuter runs Hugging Face ONNX models on any Nx backend, with no per-architecture code. Use EMLX on Apple silicon, or EXLA on CUDA or on the CPU.

The goal of Transmuter is to implement the ONNX operators in Nx. Then ONNX models run without model code.

An ONNX file contains a graph of operators. An operator is one small, standard computation, such as MatMul, Conv, Softmax, Add or Reshape. Each operator has inputs, outputs and attributes, for example the kernel size of a Conv. The ONNX standard defines about 200 operators, and a model connects a few hundred to a few thousand of them. When a model is exported, the exporter traces its forward pass and writes these operators to the file.

Transmuter implements each operator one time, as Nx operations. It then runs a model by running its graph, operator by operator. So Transmuter does not port model architectures. A new architecture needs no new Elixir code. It needs only operators that Transmuter already has.

The gap

Elixir has three ways to run Hugging Face models today. Each way has a limit:

Transmuter fills this gap. It runs the exported graph directly as Nx operations, so:

When a model has no ONNX file on Hugging Face, export it one time with Optimum. In the 300 most downloaded transformers models, 62% of downloads already have an ONNX file on Hugging Face.

Example

Transmuter.Text gives servings that take text, as in Bumblebee. A serving loads the ONNX file, the tokenizer and the configuration of a Hugging Face repository, and returns an Nx.Serving:

opts = [backend: EMLX.Backend, defn_options: [compiler: EMLX]]

serving = Transmuter.Text.text_embedding({:hf, "Xenova/all-MiniLM-L6-v2"}, opts)
Nx.Serving.run(serving, "The cat sat on the mat.")
#=> %{embedding: #Nx.Tensor<f32[384] ...>}

serving = Transmuter.Text.generation({:hf, "onnx-community/Qwen3-0.6B-ONNX"}, opts)
Nx.Serving.run(serving, %{messages: [%{role: "user", content: "What is the capital of France?"}], seed: 7})
#=> %{results: [%{text: "<think>\n...</think>\n\nThe capital of France is **Paris, France**.",
#=>               token_summary: %{input: 15, output: 167, padding: 0}}]}

The tasks:

The results have the shapes of Bumblebee or of Python transformers, and their values match the pipelines of Python transformers. The text of image_text_to_text matches ONNX Runtime on the same files. See the Pipelines guide.

Tensors

For a model that has no serving, give tensors to the graph:

model = Transmuter.load!({:hf, "Xenova/all-MiniLM-L6-v2", "onnx/model.onnx"}, backend: EMLX.Backend)

inputs = %{
  "input_ids" => Nx.tensor([[101, 7592, 2088, 102]]),
  "attention_mask" => Nx.tensor([[1, 1, 1, 1]]),
  "token_type_ids" => Nx.tensor([[0, 0, 0, 0]])
}

run = Transmuter.compile(model, inputs, compiler: EMLX)
%{"last_hidden_state" => hidden} = run.(inputs)

# Sentence embedding: mean of the token vectors, then L2 normalization.
mask = Nx.new_axis(inputs["attention_mask"], -1)
embedding = Nx.sum(Nx.multiply(hidden, mask), axes: [1]) |> Nx.divide(Nx.sum(mask, axes: [1]))
embedding = Nx.divide(embedding, Nx.LinAlg.norm(embedding, axes: [1], keep_axes: true))

The export sets the names of the inputs and outputs. model.inputs and model.outputs show them. For this export, model.outputs is ["last_hidden_state"].

Transmuter runs the graph that is in the file, and nothing more. The graph of all-MiniLM-L6-v2 stops at the token vectors, because sentence-transformers adds the pooling outside of the model. Thus the example above does the pooling. Transmuter.Text.text_embedding/2 does the tokenization, the pooling and the batching for you.

Transmuter.compile/3 compiles the model for the shapes of the inputs that you give. The function that it returns also takes inputs of other shapes: it compiles them on their first call, and keeps the most recently used shapes compiled. The :cache_size option sets how many, and the default is 8.

Transmuter.run/4 is the function that Transmuter.compile/3 compiles. It reads the ONNX graph and calls one Nx function for each operator. You can use it in two ways:

Installation

def deps do
  [
    {:transmuter, "~> 1.0"},
    {:emlx, "~> 0.5"},
    {:tokenizers, "~> 0.5"}
  ]
end

Use EXLA instead of EMLX for CUDA. The Transmuter.Text servings need tokenizers. To read image files for the Transmuter.Vision servings, add {:stb_image, "~> 1.0"}. Chat templates need {:otera, "~> 0.1"}, which renders Jinja templates in pure Elixir.

Compilation and graph breaks

Some graphs use a tensor value to set a shape or to select a branch. Transmuter.compile/3 breaks the graph at these operators, runs the break eagerly on the real values, and compiles the next segment for the shapes the values decide. Compiled segments are cached by input shapes.

See the Compiling and Graph Breaks guide for a full explanation of graph breaks, static inputs, the :cache_size option, and how to read error messages. The Backends and Performance guide explains EMLX memory management.

See the graph

Transmuter.Graph shows the graph of a model with what Transmuter does to it. The fill of a node gives its segment, and a red node is a graph break with its reason. A node with static outputs has a dashed border, and a node that replaces fused nodes has a thick border. Click a node to see its details, such as the shapes of its outputs.

The graph of Kokoro with the encoder open. The red Range node breaks the graph between segment 0 (blue) and segment 1 (green).

There are three ways to get the view:

The view loads no file from the network. For the graph as the exporter wrote it, without fusion and without a call, run mix transmuter.graph onnx-community/Kokoro-82M-v1.0-ONNX --plain.

Non-tensor values and random operators

Nx tensors hold only numbers. Transmuter keeps string tensors, sequences, and empty optionals as Elixir terms. Give string and sequence inputs as :static_inputs. A compiled function returns only tensors. To read string or sequence outputs, call Transmuter.run/4 without compilation. Random operators draw from Nx.Random, and each call of a compiled model draws new values. ImageDecoder needs the optional stb_image dependency.

See Supported Operators and Data Types for details.

Telemetry

Transmuter emits :telemetry events when it loads, compiles and runs a model. The graph break events show which segment compiles again and for which shapes, which segment the cache evicts, and how long each break operator runs. Transmuter.Telemetry lists each event with its measurements and metadata.

To log the compile work, attach the logger when your application starts:

Transmuter.Telemetry.Logger.attach()

Precision and quantization

Half-precision exports run in native f16 or bf16 by default, with half the memory of f32. compute_type: :f32 runs them in f32, as ONNX Runtime on the CPU does, for deep graphs that drift in native f16. With backend: EMLX.Backend, 4-bit and 8-bit MatMulNBits weights stay packed as MLX quantized tensors for lower peak memory and faster inference. On other backends, such as EXLA, these weights become float weights when the model loads, about 4 bytes per weight, so each call runs one matmul and does not unpack them.

See the Backends and Performance guide and the Language Models guide for details and benchmarks.

Fine-tuning

Transmuter.run/4 is plain Nx. Thus Nx.Defn.value_and_grad/2 calculates gradients through the graph, and an optimizer such as Polaris updates the params. Load the model with trainable: true. Then small weights, such as a bias with 1 element, are params and not constants.

bench/finetune.exs fine-tunes a MiniLM cross-encoder. Its gradients and its loss curve for 30 AdamW steps are the same as in PyTorch.

Fine-tune an fp32 export. Inference exports have no dropout, and quantized weights have no gradients.

Coverage

bench/coverage runs the most downloaded ONNX exports for each Hugging Face task. It compares each output with ONNX Runtime. The reference is ONNX Runtime with graph optimizations off, because its default optimizer gives wrong results for some exports. For MatMulNBits, the reference uses accuracy_level 0. At level 4, ONNX Runtime quantizes the activations to int8, and that result is an approximation.

Corpus Result
Top exports for each Hugging Face task 75 of 77 repositories, 112 of 114 files match
q4, int8 and fp16 variants of 10 repositories all 12 q4 files match; int8 and fp16 are within the noise of ONNX Runtime
Models requested in Bumblebee issues, with Apertus, a tiny Qwen3-MoE for the routing of Mixtral, GPT-J and Falcon 15 of 18 files match. The Stable Diffusion VAE encoder samples noise. The int8 Falcon and GPT-J files differ from ONNX Runtime on x86, whose int8 kernel is not exact, see below. The Falcon file matches ONNX Runtime on arm64, and the GPT-J file also has one rounding tie
Image classifiers from the ONNX Model Zoo, with the int8 VGG16, AlexNet and ZFNet 7 of 7 match
Faster R-CNN 10 and 12 and Mask R-CNN 12 from the ONNX Model Zoo 3 of 3 match
Models that Bumblebee pull requests add: GLiNER, Nomic BERT, ColBERT, MPNet, Jina BERT v2, Swin, M2M100, NLLB, Llama 3.2, Phi-3.5, SmolLM3, Qwen3-VL, Gemma 4 and Ministral 3 19 of 21 files match. The Phi-3.5 q4f16 file is within noise. The Gemma 4 decoder differs, see below. The Ministral 3 vision encoder matches at its 1540x1540 input with EMLX on the GPU of an Apple M2 Pro
Small language models: Granite 4.0-H (hybrid with Mamba-2 layers), LFM2-8B-A1B (mixture of experts), Gemma 3, Phi-4-mini, Qwen3, DeepSeek-R1-Distill, Falcon3, TinyLlama, MobileLLM-R1 and Baguettotron 11 of 11 q4 files match
SigLIP and SigLIP2: the full model, the vision model and the text model 6 of 6 match
One graph for each operator variant 255 of 255 match

Transmuter supports all 203 operators of the ONNX standard domain, and the ONNX Runtime contrib operators that Hugging Face exports use, including the mixture-of-experts operators MoE and QMoE.

These models do not match value by value. Most of them make small rounding differences large:

These models also ran from input to final result, and the result agrees with ONNX Runtime:

Model Check
Kokoro-82M Speech for inputs of up to 505 phonemes. With the atan2 fix of the example, the spectrum matches PyTorch
Grounding DINO tiny The same labels and number of detections on test/fixtures/cats.png, with scores within 0.001
MMS-TTS English With the random values of ONNX Runtime, the same audio length and a waveform within 6e-5. Without noise, text_to_speech gives the audio length of PyTorch and a waveform within 1e-4
SpeechT5 With a decoder dropout that keeps all values, text_to_speech gives the audio length of PyTorch and a waveform within 1e-4
SAM 2.1 tiny The same masks (IoU 1.0) for 1 point, 3 points, a box, and a box with a point
Apertus 0.5B Instruct generation gives the 16 greedy tokens of ONNX Runtime on the q4 export for a chat
BigBird RoBERTa base (exported with Optimum) fill_mask gives the top 5 tokens of ONNX Runtime, with scores within 1e-5, for 9 and 1809 tokens. The text of 1809 tokens needs compile: [sequence_length: 2048], because the default lengths stop at 512. The export has full attention. Above 704 tokens, Python uses block sparse attention and gives other tokens
SAM 3 tracker mask_generation gives the masks of ONNX Runtime with the processor of Python transformers 5, for 1 point, 3 points, a box, and a box with a point
Moonshine base The same tokens for 2 s, 6 s and 15 s of speech
Parakeet TDT 110M (sherpa-onnx export) The same transcripts for 2 s, 6 s and 15 s of speech, with the encoder, the LSTM decoder and the joiner in Transmuter
wav2vec2 base 960h The same CTC transcripts for 2 s, 6 s and 15 s of speech
Piper en_US lessac medium The same audio length and a waveform within 1.3e-5, with the noise scales at 0
Florence-2 base image_to_text gives the tokens of the beam search of Python transformers on the image features of the export, for 6 tasks. The vision encoder of the export differs from PyTorch, see The Florence-2 vision encoder
SmolVLM 256M The same 100 tokens for an image with 13 tiles
EmbeddingGemma 300M text_embedding within 9.3e-7 of ONNX Runtime for 9 and 543 tokens. The export attends to all tokens, but the model has a sliding window of 512 tokens. So a text of more than 512 tokens differs from sentence-transformers (cosine 0.983 at 543 tokens), and a short text differs by up to 5.6e-4
Qwen2-VL 2B With the vision features of ONNX Runtime, the q4 decoder gives the same 60 tokens for two images of different sizes. The fp32 vision encoder is within 2e-5. With the fp32 vision encoder, image_text_to_text gives the greedy text of ONNX Runtime, up to 24 tokens, for a wide image, a tall image and a chat without images
Qwen2.5-VL 3B The processor gives the patches, the prompt and the 3-D positions of Qwen2VLImageProcessor and Qwen2_5_VLProcessor for a wide and a tall image. The fp32 vision encoder, with the window attention of the export, is within 2.7e-3 of ONNX Runtime on the features of an image of 396 patches, where the largest feature is 51.8 and EMLX and EXLA differ from each other by 9.1e-4. With the fp32 vision encoder, the q4 embeddings and repetition_penalty: 1.0 (generation_config.json sets 1.05), image_text_to_text gives the greedy text of ONNX Runtime, up to 24 tokens, for a wide image, a tall image and a chat without images, and the first 9 tokens for a chat with both images, whose 10th token is a near tie in ONNX Runtime (a margin of 0.028 in the logits). The vision encoder of the export attends across the images of one call in its layers with full attention, so each image runs alone, as in PyTorch
Qwen3-VL 2B The processor gives the patches of Qwen2VLImageProcessorFast within 1e-4 on 6 values and on the mean absolute value. Two of the values change with the weights of Pillow. It also gives the prompt and the 3-D positions of transformers. With the fp32 vision encoder and the q4 embeddings, image_text_to_text gives the greedy text of ONNX Runtime, up to 24 tokens, for a wide image, a tall image, a chat with both images and a chat without images. The export has no deepstack features, so the text can differ from the PyTorch model
Ministral 3 3B The processor gives the pixel values and the prompt of PixtralImageProcessorFast and PixtralProcessor for 3 image sizes. With the q4 files, image_text_to_text gives the greedy text of ONNX Runtime for a chat with an image and a text chat. The q4 vision encoder at 1540x1540 (12100 patches) is within 3.9e-5 of ONNX Runtime. Its coverage case takes 27 s with EMLX on the GPU of an Apple M2 Pro, with the load and the compilation
Gemma 3 4B The processor gives the pixel values and the prompt of Gemma3ImageProcessor and Gemma3Processor for a wide image, a tall image and two images. With the default q4 and 8-bit files, image_text_to_text gives the greedy text of ONNX Runtime, up to 24 tokens, for a wide image, a tall image and a text chat, alone and in a batch. The q4 vision encoder at 896x896 is within 3.8e-4 (largest value 31.3) and the q4 decoder within 9.5e-3 on the logits (largest value 28.2). The decoder of the export attends causally over the image tokens, so the text can differ from the PyTorch model, which attends in both directions inside an image
LLaVA-Interleave 0.5B The processor gives the pixel values and the prompt of SiglipImageProcessor and LlavaProcessor for a wide and a tall image, and those of CLIPImageProcessor for the tiny export. With the default q4 and 8-bit files, image_text_to_text gives the greedy text of ONNX Runtime, up to 24 tokens, for a wide image, a tall image and a text chat, alone and in a batch
InsightFace buffalo_l SCRFD detection within 2.7e-5, and ArcFace embeddings with a cosine similarity of 1.0 to ONNX Runtime
RapidOCR (PaddleOCR v4) The same text for three lines of rendered text, after detection, cropping and recognition
Real-ESRGAN x4 The upscaled image within 1.3e-5
SD-Turbo The UNet within 2e-6 and the text encoder within 7.6e-5, then 4-step images
Chronos-Bolt tiny The quantile forecasts within 1.2e-6
Qwen3-0.6B FP8, exported with FLOAT8E4M3FN weights The same 30 tokens, with logits within 4.5e-5
Qwen3-0.6B with YaRN rope scaling, exported The same 30 tokens, compiled and without compilation
FaceNet (InceptionResnetV1), exported Embeddings within 1e-7
ControlNet scribble and the SD 1.5 UNet with ControlNet inputs, exported ControlNet residuals within 3.8e-5, then a scribble-to-image run

The Florence-2 vision encoder

The vision_encoder.onnx of onnx-community/Florence-2-base-ft gives other image features than PyTorch. Transmuter gives the features of ONNX Runtime, so its text is that of the export. The channel attention of DaViT multiplies the queries by tokens ** -0.5. The export computed this scale for a 224x224 input and stored it as a constant: the 12 Mul nodes /blocks.N/blocks.N.M/channel_block/channel_attn/fn/Mul have 1/56 (stage 0), 1/28 (stage 1), 1/14 (the 9 blocks of stage 2) and 1/7 (stage 3). The image processor resizes each image to 768x768, so the correct values are 1/192, 1/96, 1/48 and 1/24. The softmax of each channel attention is thus 3.43 times too sharp. The export of Xenova/tiny-random-Florence2ForConditionalGeneration has the same constants. On test/fixtures/cats.png, the image features differ from PyTorch by up to 6.1 (mean 0.44). With the correct constants, they differ by up to 1.9e-4.

The text changes for all 6 tasks of the tests (beam search with 3 beams):

Task Export PyTorch
<CAPTION> A couple of cats laying on top of a pink blanket. Two cats laying on a pink blanket next to remotes.
<MORE_DETAILED_CAPTION> There are two cats laying on top of a pink blanket. There is a white remote next to the cats. ... There are two cats laying on a pink blanket. There are remotes next to the cats on the blanket. ...
<OD> 2 cats and 2 remotes Also a bed. The other boxes move by up to 7 bins of 1/1000 of the image
<CAPTION_TO_PHRASE_GROUNDING> "two cats" 3 boxes The same 3 boxes, moved by up to 3 bins
<REFERRING_EXPRESSION_SEGMENTATION> "a cat" A polygon around the left cat, with repeated points A polygon around the right cat
<OCR_WITH_REGION> (test/fixtures/handwriting.png) Meet the at the station at noon. Meet me at the station 'at noon.

With the correct constants, the export gives the text of PyTorch for all 6 tasks. To get it, change the 12 initializers of the Mul nodes to tokens ** -0.5 for a 768x768 input, as test/support/pipeline_references.py does, and save the file.

All tested models

"Within noise" means a precision variant that differs from ONNX Runtime by its rounding noise. "Random" means a model that samples random values.

Model Task Parameters Files Compared with ONNX Runtime
onnx-community/wav2vec2-base-Speech_Emotion_Recognition-ONNX audio-classification 94.6M model matches
Xenova/ast-finetuned-audioset-10-10-0.4593 audio-classification 86.6M model matches
onnx-community/whisper-base automatic-speech-recognition 72.6M decoder_model, decoder_model_merged, decoder_with_past_model, encoder_model matches
Xenova/whisper-tiny.en automatic-speech-recognition 46M decoder_model, decoder_model_merged, decoder_with_past_model, encoder_model, decoder_model_merged_fp16, decoder_model_merged_q4, decoder_model_merged_quantized, encoder_model_fp16, encoder_model_q4, encoder_model_quantized decoder_model, decoder_model_merged, decoder_with_past_model, encoder_model, decoder_model_merged_q4, encoder_model_q4 match; decoder_model_merged_fp16, decoder_model_merged_quantized, encoder_model_fp16, encoder_model_quantized within noise
onnx-community/depth-anything-v2-base depth-estimation 97.2M model matches
onnx-community/depth-anything-v2-small depth-estimation 24.7M model matches
Xenova/depth-anything-small-hf depth-estimation 24.7M model matches
facebook/contriever (exported with Optimum) feature-extraction 109M model matches
microsoft/markuplm-base (exported with Optimum) feature-extraction 133M model matches
onnx-community/embeddinggemma-300m-ONNX feature-extraction 309M model, model_no_gather_q4, model_q4, model_quantized matches
onnx-community/Qwen3-Embedding-0.6B-ONNX feature-extraction 600M model matches
Xenova/all-MiniLM-L6-v2 feature-extraction 22.6M model, model_fp16, model_int8, model_q4, model_quantized matches
Xenova/bge-base-en-v1.5 feature-extraction 109M model matches
Xenova/bge-small-en-v1.5 feature-extraction 33.2M model matches
jinaai/jina-embeddings-v2-small-en feature-extraction 32.4M model matches
Xenova/all-mpnet-base-v2 feature-extraction 109M model matches
answerdotai/answerai-colbert-small-v1 feature-extraction 33.2M model matches
nomic-ai/nomic-embed-text-v1.5 feature-extraction 137M model matches
google/bigbird-roberta-base (exported with Optimum) fill-mask 166M model matches
microsoft/mdeberta-v3-base (exported with Optimum) fill-mask 472M model matches
Xenova/bert-base-multilingual-cased fill-mask 178M model matches
Xenova/bert-base-uncased fill-mask 110M model, model_fp16, model_q4, model_quantized model, model_fp16, model_q4 match; model_quantized within noise
Xenova/distilbert-base-uncased fill-mask 67M model matches
onnx-community/vit-base-violence-detection-ONNX image-classification 85.7M model matches
Xenova/facial_emotions_image_detection image-classification 85.8M model matches
Xenova/vit-base-patch16-224 image-classification 86.7M model, model_fp16, model_q4, model_quantized model, model_fp16, model_q4 match; model_quantized within noise
bvlcalexnet-12-int8 (ONNX Model Zoo) image-classification 61M bvlcalexnet-12-int8 matches
densenet-12 (ONNX Model Zoo) image-classification 8.15M densenet-12 matches
emotion-ferplus-8 (ONNX Model Zoo) image-classification 8.76M emotion-ferplus-8 matches
googlenet-12 (ONNX Model Zoo) image-classification 7M googlenet-12 matches
inception-v1-12 (ONNX Model Zoo) image-classification 7M inception-v1-12 matches
vgg16-12-int8 (ONNX Model Zoo) image-classification 138M vgg16-12-int8 matches
zfnet512-12-int8 (ONNX Model Zoo) image-classification 87.3M zfnet512-12-int8 matches
Xenova/swin-tiny-patch4-window7-224 image-classification 28.6M model matches
Xenova/dino-vits16 image-feature-extraction 21.7M model matches
Xenova/dinov2-small image-feature-extraction 22.1M model matches
onnx-community/ormbg-ONNX image-segmentation 44M model matches
Xenova/modnet image-segmentation 6.46M model matches
Xenova/segformer-b0-finetuned-ade-512-512 image-segmentation 3.75M model matches
MaskRCNN-12 (ONNX Model Zoo) image-segmentation 44.4M MaskRCNN-12 matches
Xenova/tiny-random-Florence2ForConditionalGeneration image-text-to-text 5.16M decoder_model, decoder_model_merged, decoder_with_past_model, embed_tokens, encoder_model, vision_encoder matches
Xenova/tiny-random-LlavaForConditionalGeneration image-text-to-text 9.44M decoder_model, decoder_model_merged, decoder_with_past_model, embed_tokens, vision_encoder matches
mistralai/Ministral-3-3B-Instruct-2512-ONNX image-text-to-text 4.29B decoder_model_merged_q4, embed_tokens_q4, vision_encoder_q4 matches
onnx-community/Qwen3-VL-2B-Instruct-ONNX image-text-to-text 2.37B decoder_model_merged_q4, embed_tokens_q4, vision_encoder matches
onnx-community/gemma-3-4b-it-ONNX image-text-to-text 4.3B decoder_model_merged_q4, embed_tokens_quantized, vision_encoder_q4 matches
onnx-internal-testing/tiny-random-Gemma3ForConditionalGeneration image-text-to-text 16.9M decoder_model_merged, embed_tokens, vision_encoder matches
onnx-community/gemma-4-E2B-it-ONNX image-text-to-text 5.64B audio_encoder_q4, decoder_model_merged_q4, embed_tokens_q4, vision_encoder audio_encoder_q4, embed_tokens_q4, vision_encoder match; decoder_model_merged_q4 differ, see below
Xenova/swin2SR-classical-sr-x2-64 image-to-image 12.1M model matches
Xenova/swin2SR-lightweight-x2-64 image-to-image 1.03M model matches
Xenova/swin2SR-realworld-sr-x4-64-bsrgan-psnr image-to-image 12.1M model matches
onnx-community/manga-ocr-base-ONNX image-to-text 115M decoder_model, encoder_model matches
Xenova/trocr-small-handwritten image-to-text 61.4M decoder_model, decoder_model_merged, decoder_with_past_model, encoder_model matches
Carve/LaMa-ONNX inpainting 51M lama_fp32 matches
onnx-community/vitpose-base-simple keypoint-detection 85.9M model matches
onnx-community/sam3-tracker-ONNX mask-generation 473M prompt_encoder_mask_decoder, vision_encoder matches
Xenova/sam-vit-base mask-generation 187M model, prompt_encoder_mask_decoder, vision_encoder matches
Xenova/slimsam-50-uniform mask-generation 28M prompt_encoder_mask_decoder, vision_encoder matches
Xenova/slimsam-77-uniform mask-generation 9.73M prompt_encoder_mask_decoder, vision_encoder matches
Xenova/tiny-random-RoFormerForMultipleChoice multiple-choice 1.64M model matches
onnx-community/rfdetr_nano-ONNX object-detection 26.9M model matches
Xenova/detr-resnet-50 object-detection 41.7M model, model_fp16, model_q4, model_quantized model, model_q4 match; model_fp16, model_quantized within noise
Xenova/yolos-tiny object-detection 6.49M model matches
FasterRCNN-10 (ONNX Model Zoo) object-detection 41.8M FasterRCNN-10 matches
FasterRCNN-12 (ONNX Model Zoo) object-detection 44.1M FasterRCNN-12 matches
onnx-community/roberta-base-squad2-ONNX question-answering 124M model matches
Xenova/distilbert-base-cased-distilled-squad question-answering 65.2M model matches
Xenova/distilbert-base-uncased-distilled-squad question-answering 66.4M model matches
onnx-community/paraphrase-multilingual-MiniLM-L12-v2-ONNX sentence-similarity 118M model matches
onnx-community/ruri-v3-30m-ONNX sentence-similarity 36.7M model matches
onnx-community/USER2-small-ONNX sentence-similarity 34.4M model matches
onnx-community/Text-Summarizer-t5-small-ONNX summarization 93.4M decoder_model, decoder_model_merged, decoder_with_past_model, encoder_model matches
onnx-community/text_summarization-ONNX summarization 77M decoder_model, decoder_model_merged, decoder_with_past_model, encoder_model matches
onnx-community/distilbert-multilingual-toxicity-classifier-ONNX text-classification 135M model matches
onnx-community/ModernBERT-base-nli-ONNX text-classification 150M model matches
SamLowe/roberta-base-go_emotions (exported with Optimum) text-classification 125M model matches
Xenova/ms-marco-MiniLM-L-6-v2 text-classification 22.7M model matches
HuggingFaceTB/SmolLM2-135M-Instruct text-generation 135M model, model_fp16, model_q4, model_q4f16, model_quantized model, model_q4 match; model_fp16, model_q4f16, model_quantized within noise
HuggingFaceTB/SmolLM2-360M-Instruct text-generation 362M model matches
onnx-community/gemma-3-270m-it-ONNX text-generation 453M model, model_fp16, model_q4, model_quantized model, model_q4, model_quantized match; model_fp16 within noise
onnx-community/LFM2-350M-ONNX text-generation 430M model, model_q4, model_quantized matches
onnx-community/Qwen2.5-0.5B-Instruct text-generation 498M model matches
onnx-community/Qwen3-0.6B-ONNX text-generation 601M model, model_q4, model_quantized model, model_q4 match; model_quantized within noise
tiny random Falcon (exported with Optimum) text-generation 4.18M model matches
tiny random GPT-J (exported with Optimum) text-generation 131K model matches
onnx-community/Apertus-v1.1-0.5B-Instruct-ONNX text-generation 573M model_q4 matches
onnx-internal-testing/tiny-random-ApertusForCausalLM text-generation 4.21M model, model_q4 matches
onnx-internal-testing/tiny-random-Qwen3MoeForCausalLM text-generation 11.3M model matches
Xenova/falcon-rw-1b text-generation 1.31B decoder_model_merged_quantized matches ONNX Runtime on arm64, differs on x86, see above
Xenova/slovak-gpt-j-405M text-generation 414M decoder_model_merged_quantized differs: x86 saturation and a rounding tie, see above
Xenova/gpt2 text-generation 127M model matches
Xenova/llama2.c-stories15M text-generation 30.4M decoder_model, decoder_model_merged, decoder_with_past_model, model matches
Xenova/tiny-random-GemmaForCausalLM text-generation 2.05M model matches
Xenova/tiny-random-Phi3ForCausalLM text-generation 2.07M model matches
onnx-community/Baguettotron-ONNX text-generation 321M model_q4 matches
onnx-community/DeepSeek-R1-Distill-Qwen-1.5B-ONNX text-generation 1.79B model_q4 matches
onnx-community/Falcon3-1B-Instruct text-generation 1.67B model_q4 matches
onnx-community/gemma-3-1b-it-ONNX text-generation 1.32B model_q4 matches
onnx-community/granite-4.0-h-1b-ONNX text-generation 1.97B model_q4 matches
onnx-community/granite-4.0-h-350m-ONNX text-generation 490M model_q4 matches
onnx-community/LFM2-8B-A1B-ONNX text-generation 4.85B model_q4 matches
onnx-community/MobileLLM-R1-950M-ONNX text-generation 952M model_q4 matches
onnx-community/Phi-4-mini-instruct-ONNX text-generation 4.46B model_q4 matches
onnx-community/Qwen3-1.7B-ONNX text-generation 1.73B model_q4 matches
Xenova/TinyLlama-1.1B-Chat-v1.0 text-generation 1.1B model_q4 matches
onnx-community/Phi-3.5-mini-instruct-onnx-web text-generation 3.83B model_q4f16 within noise
onnx-community/Llama-3.2-1B-Instruct-ONNX text-generation 1.24B model_q4 matches
HuggingFaceTB/SmolLM3-3B-ONNX text-generation 3.08B model_q4 matches
onnx-community/stable-diffusion-v1-5-ONNX text-to-image 1.07B text_encoder/model, unet/model, vae_decoder/model, vae_encoder/model text_encoder/model, unet/model, vae_decoder/model match; vae_encoder/model random
onnx-community/Kokoro-82M-v1.0-ONNX text-to-speech 81.2M model differs, see below
onnx-community/granite-timeseries-patchtsmixer time-series-forecasting 196K model matches
onnx-community/granite-timeseries-patchtst time-series-forecasting 616K model matches
Xenova/bert-base-multilingual-cased-ner-hrl token-classification 177M model matches
Xenova/bert-base-NER token-classification 108M model matches
Xenova/distilbert-base-multilingual-cased-ner-hrl token-classification 135M model matches
onnx-community/gliner_small-v2.1 token-classification 153M model matches
Xenova/opus-mt-en-ROMANCE translation 111M decoder_model, decoder_model_merged, decoder_with_past_model, encoder_model matches
Xenova/m2m100_418M translation 617M decoder_model_merged, encoder_model matches
Xenova/nllb-200-distilled-600M translation 1.06B decoder_model_merged_q4, encoder_model_q4 matches
onnx-community/timesformer-base-finetuned-k400 video-classification 122M model matches
onnx-community/silero-vad voice-activity-detection 545K model matches
onnx-community/smart-turn-v3-ONNX voice-activity-detection 8M model matches
Xenova/distilbert-base-uncased-mnli zero-shot-classification 67M model matches
Xenova/mobilebert-uncased-mnli zero-shot-classification 24.6M model matches
Xenova/nli-deberta-v3-xsmall zero-shot-classification 70.8M model matches
Xenova/chinese-clip-vit-base-patch16 zero-shot-image-classification 188M model matches
Xenova/mobileclip_s0 zero-shot-image-classification 53.8M text_model, vision_model matches
Xenova/mobileclip_s2 zero-shot-image-classification 99.1M text_model, vision_model matches
Xenova/siglip-base-patch16-224 zero-shot-image-classification 203M model, text_model, vision_model matches
onnx-community/siglip2-base-patch16-224-ONNX zero-shot-image-classification 375M model, text_model, vision_model matches
onnx-community/grounding-dino-tiny-ONNX zero-shot-object-detection 179M model differs, see below
Xenova/owlv2-base-patch16-ensemble zero-shot-object-detection 153M model matches
Xenova/owlvit-base-patch32 zero-shot-object-detection 153M model matches

To run the matrix:

  1. Run bench/coverage/select_models.py to select the models.
  2. Run bench/coverage/record_references.py to download them and record the ONNX Runtime outputs.
  3. Run bench/coverage/run_cases.py to run each case through Transmuter.
  4. Run bench/coverage/print_matrix.py to show the matrix, and bench/coverage/list_models.py to make the table of tested models.

If a case does not match, run bench/coverage/trace_ort.py and bench/coverage/find_divergence.exs. They show the first node where Transmuter and ONNX Runtime give different values.

Tests

mix test downloads five models and compares them with ONNX Runtime. test/support/model_references.py shows the reference values.

mix test also runs one small graph for each operator variant in test/fixtures/ops, with no download. test/support/make_op_fixtures.py makes these graphs and records their outputs from ONNX Runtime, or from the ONNX reference evaluator when ONNX Runtime does not have the operator. For random operators, the tests check the shape, the type and the distribution of the values.

On a Mac, TRANSMUTER_TEST_DEVICE=cpu mix test uses the EMLX CPU device. But EMLX 0.5 runs compiled functions on the GPU when Metal is available, also with device: :cpu. Thus only the Linux CI tests the CPU fully.

License

Apache-2.0