ONNX Is a Package Format, Not a Model — Xenova Didn't Build BERT
Who trained it and what box it ships in are two separate questions
ONNX isn't a model. It's a package format that holds the compute order (the graph) and the weights in one file. Who trained a model and how it's packaged for distribution are separate things. Once that clicks, you can answer "will this model run in the browser?" from a repo's file list instead of its name.
How this came up: no Korean BERT
I built an experiment app that runs six BERT-family models in the browser with transformers.js. The only usable ones were ONNX exports like Xenova/.... klue/bert-base, a Korean BERT, had zero .onnx files according to the Hugging Face API, so it couldn't go in (checked 2026-09-19).
Then came the question: "So did Xenova do the pre-training and fine-tuning?" Easy to mix up. The repo name blends the maker and the packager.
Unpacking one repo name
Xenova/bert-base-multilingual-uncased-sentiment went through four steps.
Training ended in the first two. Google pre-trained on Wikipedia in 104 languages, and NLP Town fine-tuned it on product review stars. Xenova moved the same weights into an ONNX box and also uploaded quantized versions (q8, fp16: storing the numbers in smaller formats). transformers.js runs that with ONNX Runtime (WASM or WebGPU).
Compared with a PyTorch checkpoint
PyTorch checkpoint (.bin, .safetensors) |
ONNX (.onnx) |
|
|---|---|---|
| What's inside | Weights (numbers) only | Compute graph + weights |
| To run it | Python + the model class code (transformers library) | Just ONNX Runtime |
| Where it runs | Mostly Python servers | Browser (WASM, WebGPU), C#, Java, mobile and more |
| Limit I hit | — | Only the outputs chosen at export time. I couldn't get per-layer hidden states or attention values out |
That last row actually hurt. I wanted the app to show how a sentence changes inside the encoder, but the ONNX export only exposed the final layer. The packaging step decides how many doors the box has.
Each file has its own job, too.
| File | Role | Which step produced it |
|---|---|---|
tokenizer.json |
Vocabulary | Built from the pre-training data |
config.json |
Layers, dimensions, heads: the blueprint numbers | Model design |
onnx/model_*.onnx |
Weights + compute graph | Pre-training (+ fine-tuning) |
Numbers I measured (2026-09-19, macOS, Chrome, WebGPU shader-f16 available)
For Xenova/paraphrase-multilingual-MiniLM-L12-v2: model_fp16.onnx 235MB, model_quantized.onnx 118MB, tokenizer.json 17MB. Same weights, twice the size depending on packaging.
I embedded 372 document chunks with that MiniLM. WebGPU + fp16 took about 21 seconds, first download included. WASM + q8 ran single-threaded (crossOriginIsolated=false) and was around 25% after a minute. I didn't wait for it to finish.
A few more snags.
The npm package is heavy. Installing @huggingface/transformers 4.3.0 from npm pulls in onnxruntime-node (289MB in node_modules) and sharp. I only needed the browser, so I switched to a version-pinned import from a CDN (jsDelivr).
Files don't show up on disk by name. The browser stores downloads in Cache Storage under transformers-cache, keyed by request URL. Searching for config.json finds nothing.
So when you pick a model
If it has to run in the browser, first check the repo's Files tab for an onnx/ folder. No folder means you convert it yourself. The trainer and the packager can differ, so check the license and training data on the original repo's model card (nlptown/... in the example above).
If you're stuck deciding whether a model should run in the browser or on a server, or what to do when the model you need has no export, feel free to ask negabaro. We've run the experiment above ourselves and can look at it with you.
Key Concepts
The first part of a repo name may be the packager. Find the original model on the model card
For the browser, check the Files tab for an onnx/ folder first
Same weights, but fp16 vs q8 doubles the size. You trade it against speed and accuracy
If you need inner-layer values, choose the outputs at export time. Default exports only give the last layer