# Development / reproducing the conversion This repo is an ONNX conversion of [`finegrain/finegrain-box-segmenter`](https://huggingface.co/finegrain/finegrain-box-segmenter) (MVANet, Swin-B backbone, MIT). Everything a *user* needs is on the [model card](../README.md); this note is for anyone who wants to reproduce the export or understand how it was built. The full, sourced write-up is [`HOW-CONVERSION-WAS-MADE.md`](HOW-CONVERSION-WAS-MADE.md); measured cross-machine numbers are in [`PERFORMANCE-BENCHMARK.md`](PERFORMANCE-BENCHMARK.md). ## What the graph is The published v0.1 weights are the **3-channel, crop-based** MVANet (not the blog's later 5-channel variant). The box prompt in the original `BoxSegmenter` is applied **host-side as a crop** and is never fed to the network, so the exported ONNX graph is exactly `image → logits`: - input `input` `float32[1,3,1024,1024]` → output `logits` `float32[1,1,1024,1024]` (raw logits — apply sigmoid). - static 1024² (Swin hard-codes it); batch fixed at 1. This release drives it for **whole-image** background removal (no box). The exact pre/post-processing and I/O contract are on the model card. ## Reproducing the export Python 3.12 via [uv](https://github.com/astral-sh/uv). On Windows set `PYTHONUTF8=1` first, so torch's progress emoji doesn't crash a cp1250 console. ```powershell uv venv --python 3.12.10 uv pip install "git+https://github.com/finegrain-ai/refiners.git" onnx onnxruntime onnxscript pillow numpy huggingface_hub safetensors $env:PYTHONUTF8='1' # export (legacy TorchScript exporter; the dynamo path fails decomposing Swin's transpose+reshape. # --patch-pool swaps adaptive_avg_pool2d -> avg_pool2d, an exact equivalent here) python python/export_onnx.py --exporter legacy --opset 17 --patch-pool # -> models/mvanet_box_segmenter.onnx (published here as onnx/model.onnx) python python/verify_parity.py # torch vs ORT python python/optimize_onnx.py # optional -> models/..._fp16.onnx (not shipped) ``` `verify_parity.py` compares against refiners' golden reference image; that reference isn't shipped here, so pull it from refiners / the base model to re-run parity. Result: random-input logits `max|Δ| = 1.5e-5`, golden cactus mask **MAE 0.000 / 255** (bit-identical to PyTorch). ## Why the two non-obvious choices (full detail in the write-up) - **Legacy exporter, not dynamo** — dynamo's `torch.export` can't decompose Swin's `transpose(1,2).reshape(...)` (it lowers the non-contiguous view to a strict `aten.view`). - **`adaptive_avg_pool2d` → `avg_pool2d`** — refiners always pools with even divisors, so it is a numerically exact swap that maps to ONNX `AveragePool`. ## Not included in this repo The box-prompt host wrapper (`segment.mjs`), the fp16 build, the sourced research notes and the portable benchmark pack live in the development history, not in this release repo — it is positioned as a whole-image background remover. Open a Discussion if you want any of them.