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:
- Bumblebee runs models with great accuracy on all Nx backends. But a person must port each architecture to Axon by hand, and a port takes weeks of work and review. Hugging Face gets new architectures faster than people can port them.
- Ortex runs ONNX models with ONNX Runtime. But it runs outside of Nx: you cannot put it in a
defnfunction, it has no Apple silicon GPU path, and it is in maintenance mode. - axon_onnx changed ONNX graphs into Axon models. It stopped in 2024. It set all shapes when it loaded a model, and many ONNX graphs need shapes that change.
Transmuter fills this gap. It runs the exported graph directly as Nx operations, so:
- Models that Bumblebee does not have run with no new code. Examples are SAM, Depth Anything, Grounding DINO, OWLv2, Florence-2, RF-DETR, Kokoro and Stable Diffusion.
- The result is plain Nx. It runs on EMLX and EXLA, it works with
Nx.Serving, andNx.Defn.gradcan differentiate it. - Shapes are set when the function compiles, not when the model loads. Thus decoders with a KV cache and graphs with
Ifbranches work.
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:
Transmuter.Texttext_embedding: MiniLM, BGE, EmbeddingGemma and Qwen3-Embeddingtext_classification: DistilBERT and other sequence classifierstext_ranking: MS MARCO cross-encoders and Qwen3-Rerankerzero_shot_entity_recognition: GLiNER (span and token models)zero_shot_classification: DistilBERT MNLI and other NLI modelstoken_classification: BERT NERfill_mask: DistilRoBERTaquestion_answering: DistilBERT SQuADgeneration: SmolLM2, SmolLM3, Qwen3 and Gemma 3text_to_text: T5 and Marian, and NLLB and M2M100 for translation between many languages
Transmuter.Visionimage_classification: ViTimage_embedding: CLIP, SigLIP and SigLIP2zero_shot_image_classification: CLIP, SigLIP and SigLIP2image_to_text: ViT-GPT2, TrOCR and Florence-2image_text_to_text: chats about images with SmolVLM, Qwen2-VL, Qwen2.5-VL, Qwen3-VL, Ministral 3, Gemma 3 and LLaVAobject_detection: DETR, YOLOS, RT-DETR and RF-DETRzero_shot_object_detection: OWL-ViT, OWLv2 and Grounding DINOdepth_estimation: Depth Anythingimage_segmentation: Segformerbackground_removal: ORMBGmask_generation: SAM, SAM 2 and the SAM 3 tracker, with point and box promptsimage_to_image: Swin2SR super resolution
Transmuter.Audiospeech_to_text: Whisperaudio_classification: AST and wav2vec2zero_shot_audio_classification: CLAPtext_to_speech: VITS and SpeechT5
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:
- Call it directly. Each operator runs immediately, one after the other. This is eager execution. It is slower, but the model can read values while it runs.
- Give it to
Nx.Defn.jit/2orNx.Defn.value_and_grad/2. Nx then records the operators as one graph, and the backend compiles that graph.
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.
There are three ways to get the view:
- In Livebook, call
Transmuter.Graph.as_graph(model, inputs: inputs). It needs the optionalkinodependency. - Outside Livebook, call
Transmuter.Graph.to_html(model, "graph.html", inputs: inputs)and open the file in a browser. - From the command line, run
mix transmuter.graph onnx-community/Kokoro-82M-v1.0-ONNX --run --open. It calls the model once with inputs of ones.
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:
- Grounding DINO selects 900 of 13,294 queries with
TopK, and some scores differ by only 9.5e-7. All operators beforeTopKmatch. Ontest/fixtures/cats.pngwith three labels, Transmuter gives the same labels and number of detections as ONNX Runtime, with scores within 0.001. - The Gemma 4 E2B decoder has activations up to about 1300 and final norm weights up to about 700. When the test changes the float inputs of ONNX Runtime by 1e-7 of their values, about one f32 rounding step, the logits of ONNX Runtime change by up to 5.5. The logits of Transmuter differ from ONNX Runtime by 5.3 on the CPU and by 13.6 on the GPU, which is the same noise. The two give the same token for 7 of 8 positions of the random test input. That input has random embeddings with sigma 0.5, without the scale of the real embeddings. With a real prompt of 20 tokens through
embed_tokens_q4anddecoder_model_merged_q4, Transmuter on the GPU gives the 32 greedy tokens of ONNX Runtime on the CPU, with logits within about 1e-4 at each step, also at a step where the top two logits of ONNX Runtime are only 0.055 apart. - Kokoro sums a phase over all audio samples and takes its STFT. ONNX Runtime and MLX round that sum differently. Also, the
atan2of the export gives a different phase for an exact STFT than for the STFT of ONNX Runtime (see The atan2 fix). When the test gives theSinandSTFToutputs of ONNX Runtime to Transmuter, all other operators match. With theatan2fix of the example, the spectrum of the speech matches PyTorch, with no tone at 4.8 kHz. - The int8 export of falcon-rw-1b uses
DynamicQuantizeLinearandMatMulInteger. The ONNX Runtime reference ran on x86 with AVX2 and no VNNI. There, theMatMulIntegerkernel of ONNX Runtime adds each pair of u8 × s8 products in int16, and a pair above 32767 saturates. Transmuter gives the exact int32 sum, which is the result that the operator defines. Layers 0 to 2 match, and one of the 6144 values of the layer 3query_key_valueproduct differs by 912. A numpy product with the int16 saturation gives the output of ONNX Runtime exactly, and the exact product gives that of Transmuter. The logits then differ by up to 1.46. ONNX Runtime on arm64 (macOS) gives the result of Transmuter, with logits within 1.9e-5. - The int8 export of slovak-gpt-j-405M differs from ONNX Runtime on the same machine, with logits up to 0.83 apart, for two reasons. First, 9 of its 145
MatMulIntegernodes, in theIfbranch of the merged decoder, saturate on x86, from the layer 0k_projon. For each of them, a numpy product with the int16 saturation gives the output of ONNX Runtime exactly, and Transmuter gives the exact product. Second, on arm64, where all 145 products are exact, oneDynamicQuantizeLinearvalue of layer 8 rounds to the other integer. Its x / scale is -1.4999986, only 1.4e-6 from a .5 tie, so the float differences of about 1e-6 between two machines decide the rounding. That one quantum grows to logits 0.74 apart. This is rounding noise, not a bug.
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:
- Run
bench/coverage/select_models.pyto select the models. - Run
bench/coverage/record_references.pyto download them and record the ONNX Runtime outputs. - Run
bench/coverage/run_cases.pyto run each case through Transmuter. - Run
bench/coverage/print_matrix.pyto show the matrix, andbench/coverage/list_models.pyto 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