Simplify, optimize, and inspect ONNX models with onnxsim and onnx-optimizer, compiled to WebAssembly. Everything runs locally in your browser — models are never uploaded.
Also experimental: a
Pyodide (Python-in-the-browser) demo
running full onnxsim.simplify(), or onnxsim's native
quantization passes on their own; see
docs/wasm_pyodide.md
for the toolchain story behind it.
Paste an ONNX model or graph in the
textual representation
— the form onnx.parser.parse_graph / parse_model accept.
It is parsed into a model by the WebAssembly module (a bare graph is
wrapped into a model with a default-domain opset import) and shown in
the Before Netron pane. Local function definitions
(<domain: "..."> name (...) => (...) {...} blocks after
the graph) are parsed too, and the wrapped model gains an opset import
for each function's domain so it validates. With convert after
parsing on, it is also run straight through the currently selected
convert mode below (Simplify / Optimize / Inline functions, or a single
debug pass).
.onnx.safetensors or .onnx.gguf archive
(e.g. one downloaded from the format selector below) -- it is decoded back to ONNX before converting.
onnxmodelzoo org; any owner/repo
or direct .onnx URL works too. Picking a model (or
entering a repo id / URL) shows its download size first, so you
can see how big it is before fetching. When a repo holds more than
one .onnx, the largest is auto-detected; use the
file selector to convert a different one.
onnxmodelzoo) don't need one. Create one at
huggingface.co/settings/tokens
("read" access is enough). It is sent straight from your
browser to huggingface.co as an
Authorization header for size checks, file
listing, and the download itself — never anywhere else, and
never included in the "Copy shareable link" URL above. With
remember off (the default) it lives only in this
page's memory and is gone on reload; checking it saves the
token in this browser's local storage (still local to your
machine) so it survives a reload.
?model=onnxmodelzoo/resnet18d_Opset18 or
?model=https://…/foo.onnx&optimizer=optimize&cf=0.
It loads and converts automatically (add &autoload=0
to only prefill). Keys: model
(hf/url), graph (an ONNX text
graph to parse), optimizer (a.k.a. mode/processor; the
convert mode —
simplify/optimize/optimize_fixed/inline/infer_shapes/data_propagation/fold_constant),
constant_fold/cf,
shape_inference/si,
inline_functions/inline,
tensor_size_threshold/tst,
target_opset/opset,
autorun/autoinfer (auto-run inference
after each conversion),
backend (prefills the backend-test URL). Setting an
input updates this URL so it stays shareable (uploads excepted).
@huggingface/hub, reconstructing the
file from content-addressed chunks and caching them in
IndexedDB so repeated / overlapping loads reuse them. On by
default; falls back to a direct download automatically if the
Xet path isn't available (e.g. a non-Xet repo or a CORS block).
XetBlob)
fetches multiple content-addressed blocks at once and adapts
the connection count to measured throughput, up to
max connections, to saturate your link — this is what
makes Xet fast enough to be the default (~10 or fewer is
usually best; 1 forces a single serial
connection). Xet also gives chunk dedup / cache reuse across
repeated or overlapping loads.
model.onnx and the test data
(test_data_set_N/ of input_*.pb /
output_*.pb) from GitHub, runs the model through
onnxruntime-web with the test inputs, and checks each output
against the expected tensor within tolerance. Pick a preset above or
paste a GitHub tree / raw.githubusercontent.com
URL; listing uses GitHub's public (rate-limited) API. With
convert the model on, the fetched model is also run through the
selected convert mode below (and shown in the Netron panes).
Quantizes a model with onnxsim's WebAssembly module, entirely in
your browser -- nothing is uploaded. Its result is kept
independent of the plain Convert section above (it never
overwrites the "after" Netron pane or converted
inference source) so you can inspect all three stages side by
side: original -- onnxsim-simplified (Convert
section, optional) -- quantized (its own "Quantized"
Netron pane and quantized inference source below).
Pick what to quantize:
Before picking a method, you can check whether INT8 (Dynamic / Static / QOperator's scheme) looks numerically safe for this model -- a static analysis of its own weights and shapes, no execution or calibration data needed, so it runs instantly.
Pick a method:
DynamicQuantizeLinear. No calibration
needed.{-1, 0, 1} × scale), e.g. BitNet-style models.
No calibration needed.QuantizeLinear/DequantizeLinear
pairs using a calibrated activation range; the graph
still computes in float32, and a QDQ-aware runtime fuses the
pattern into an integer kernel. Needs calibration (below).QLinearMatMul, computing directly in int8; also
needs a calibrated output range, not just the
activation. Needs calibration (below).Cast keeps the model's own external
input/output types float32) is simply rounded to a
narrower floating-point format, not an integer scheme, so
no calibration data is needed at all. Float16/BFloat16
halve storage; Float8 (E4M3 or E5M2, below) quarters it,
at the cost of the least precision of any format here.
Calibration (Static/QOperator only) runs the model over a
few batches of synthetic random input through
onnxruntime-web
(downloaded on demand, same as the "Run inference" panel below)
to observe each quantizable tensor's actual value range -- the
same approach onnxsim.calibrate() uses in Python,
just run here in the browser instead. Random data is a
reasonable stand-in when representative data isn't at hand, but
real calibration data (e.g. via the Python API) usually gives
tighter, more accurate ranges.
Renders the model graph with a self-hosted
Netron:
the original model on the left and the simplified/optimized result on
the right, so you can compare them side by side. Nothing is uploaded —
the model bytes are posted straight into the embedded Netron in your
browser, so there is no model-size limit. Use export SVG to save
the rendered graph as an SVG image.
The quantized result has its own Netron preview inside the
Quantize panel above instead of a third
pane here, so it never crowds this before/after comparison.
Netron is driven here purely as an embeddable model-preview component
over a postMessage protocol; that idea and protocol are
being discussed upstream in
lutzroeder/netron#1591.
Lists the model's symbolic axis names (ONNX
dim_params) on its graph inputs and outputs —
before and after simplify/optimize — so you can see how
the conversion changed the dynamic axes: a batch /
sequence axis kept symbolic, or a symbolic dimension
folded to a fixed size. Read straight from the model bytes in your
browser (nothing is uploaded); symbolic axes are shown
highlighted.
Try it:
load
Xenova/all-MiniLM-L6-v2
(a ~21 MB quantized MiniLM sentence-transformer) — its inputs
and output carry the named dynamic axes batch_size and
sequence_length, so they show up here on both the
before and after sides. Most image models (e.g. the
onnxmodelzoo ResNets in the dropdown) are fully static
and list no dim_params.
.log file. Report an issue opens a pre-filled
GitHub issue
with the selected options, browser info, and the tail of the
console output. Please also attach your model (and the downloaded
log) so the problem can be reproduced.
Profiling is on by default (“profile simplification” above): convert a model with Simplify and the fixed-point transforms (shape inference, onnx-optimizer, constant folding) — with ONNX Runtime's own per-operator constant-folding spans merged in — render below as a flame graph. Download the JSON, or click Embed in Perfetto to open the full Perfetto timeline inline on this page.
How many nodes the graph held right after every round of each simplification fixed-point loop, until it stopped changing (a fixed point) or hit the round cap. One chart per loop — Optimize (shape inference + onnx-optimizer passes) usually does the most rounds; FoldConstant is the outer pipeline wrapping it.
Runs a model for a few iterations to check it executes, using dummy
inputs generated from the model's input shapes. The model
picker chooses what to run: compare (before vs after
conversion) — the default — runs both the original upload
and the converted result on the same deterministic input and
reports how far their outputs diverge (max |Δ|) plus the speed
difference, so you can confirm the simplify/optimize step preserved
the model's numerics and see how much faster it got. You can also run
just the converted result or the original upload on
their own. The batch control sizes the model's dynamic batch
(first) axis; it has no effect on inputs whose first dimension is
fixed. The warmup control runs that many untimed passes before
the timed iterations, so one-time costs (kernel/program compilation,
buffer allocation) don't add noise to the reported latency; set it to
0 to time every run. WebGPU falls back to WebAssembly when
unavailable. Tick auto-run after each conversion to run this
panel automatically whenever a conversion finishes (with the options
selected here), instead of clicking Run inference each time.
The input fill picker's sample data option feeds real
data instead of synthetic numbers: a photo from
frgfm/imagenette
for an image-shaped input, or a tokenized sentence from
stanfordnlp/sst2
for a recognized NLP model's input_ids/
attention_mask/token_type_ids — both
fetched live from Hugging Face for each run. Any input that doesn't
match one of those shapes, or an NLP model whose tokenizer family
isn't recognized, falls back to random fill (logged in the output
below). With sample data selected, a run also shows what it
actually fed the model and got back — the fetched image/sentence
next to the model's raw output, decoded as top-5 ImageNet-1k
predictions when the output looks like a standard 1000-way
classifier (a best-effort guess, not verified per model).
The execution-provider picker also offers
WebNN
(GPU / NPU / CPU), which runs the model through the browser's own
ML stack. WebNN is experimental and ships behind a flag
(Chrome/Edge: chrome://flags/#web-machine-learning-neural-network),
with the broadest support on Windows — the WebNN status line
below the controls reports whether it is usable in your browser, and
any WebNN run falls back to WebAssembly when the chosen device or an
operator is unsupported.
If the model carries onnxsim's MAC/FLOP metrics in its
metadata_props (onnxsim
PR #527,
e.g. annotated via onnxsim.model_info.annotate_metadata),
they are shown below with the achieved throughput (GFLOP/s = model
FLOPs ÷ average latency). With annotate model info on (the
default), the converter bakes these metrics into both the
converted result and the original upload, so you can run each
and compare their MACs alongside the measured inference speed.
With profiling on (default), the run captures an onnxruntime-web
trace: on WebGPU, one span per GPU kernel invocation; on WASM (or as
a fallback), one span per session.run iteration. It
renders below in the same viewer, with an Embed in Perfetto
button to open the full timeline inline.