I wanted to run a fraud model inside a browser tab.
So I wrote an ONNX runtime from scratch — a C++ engine, and a tiny Rust one for the tab.
The prompt was small and specific. Score a transaction as fraud or not-fraud, on the client, without shipping the features to a server. Client-side, because the demo brief said "no server calls," and because sending raw account balances across the wire to a hosted inference endpoint has real consequences under PCI-DSS.
The obvious answer is to wrap an existing runtime. I wanted to understand what a runtime actually does — parse the graph, order it, run the kernels — so I wrote one instead, and kept the browser part as small as the fraud model allows.
Two hard problems that don't show on a benchmark chart.
Hand-writing the protobuf decoder.
.onnx files are protobuf-encoded. The obvious path is to run protoc against the ONNX .proto schema and get generated C++ or Rust readers for free. I didn't want that dependency in the build. So I wrote a decoder from the wire-format spec: read a tag byte, split it into a field number and a wire type, dispatch on the wire type — varint, 64-bit, length-delimited, 32-bit — and repeat. The whole reader is a few hundred lines. It understands only the fields Crucible actually uses; unknown fields are skipped correctly, which is what the spec asks for.
Two runtimes, honestly scoped.
The C++ core uses Eigen for its matrix products and im2col convolutions and implements 20 ONNX operators — enough for ResNet18 and MobileNetV2. Its outputs are checked against ONNX Runtime in a 70-case parity suite. It is not faster than ONNX Runtime: single-threaded it is several times slower on those CNNs, and the benchmark page shows the measured gap. The browser runtime is a separate, much smaller Rust interpreter with 5 operators (MatMul, Add, Relu, Sigmoid, Softmax): it runs the fraud model, not CNNs, and rejects anything else with a clear error.
What I'd do next.
Close the native performance gap with ONNX Runtime (threading, kernel fusion, faster convolution paths). Grow the WASM runtime past five operators so more than the fraud model runs in the browser, and add a parity check between the two runtimes on the models they share.