MoonViT-SO-400M β ONNX
An ONNX export of moonshotai/MoonViT-SO-400M,
the native-resolution vision encoder from Kimi-VL (initialized from and continually pre-trained on
SigLIP-SO-400M). Built with the standalone MoonVit.py script (build / eval / inference).
Parity vs the original PyTorch model: cosine = 1.000000 (see eval below).
What's in this folder
| File | Description |
|---|---|
moonvit.onnx |
The exported graph. Legacy export = single file (~1.7 GB); dynamo export = graph + moonvit.onnx.data (external weights). |
manifest.json |
Export metadata β grid, I/O shapes, normalization, which exporter was used. |
README.md |
This file. |
Important: fixed-grid export
MoonViT packs patches NaViT-style and its internals branch on grid_hws.tolist() (per-image
Python loops for the interpolated position embedding, the 2D RoPE, and the 2Γ2 patch merger), so
the graph is data-dependent on the image grid. This export bakes one fixed grid as a
constant. The default is 28Γ28 patches = 392Γ392 px; check manifest.json for the exact grid
this file was built with. For other input resolutions, export another variant
(--grid-h/--grid-w).
- Input
pixel_values:float32 [L, 3, 14, 14]βL = grid_h Γ grid_wpacked 14Γ14 patches, raster order, normalized with mean/std = 0.5. - Output
image_features:float32 [L/4, 4, 1152]β merged tokens after the 2Γ2 patch merger (e.g. 28Γ28 β[196, 4, 1152]).
Usage (onnxruntime)
import json, numpy as np, onnxruntime as ort
from PIL import Image
d = "." # this folder
man = json.load(open(f"{d}/manifest.json"))
gh, gw, P = man["grid_h"], man["grid_w"], man["patch_size"]
# preprocess an image to the export grid (raster-order packed patches, mean/std 0.5)
img = Image.open("figures/demo.png").convert("RGB").resize((gw * P, gh * P), Image.BICUBIC)
x = (np.asarray(img, np.float32) / 255.0 - 0.5) / 0.5 # [H,W,3]
x = x.transpose(2, 0, 1).reshape(3, gh, P, gw, P).transpose(1, 3, 0, 2, 4)
pixel_values = np.ascontiguousarray(x.reshape(gh * gw, 3, P, P))
sess = ort.InferenceSession(f"{d}/{man['file']}", providers=["CPUExecutionProvider"])
feats = sess.run(None, {"pixel_values": pixel_values})[0] # [gh*gw/4, 4, 1152]
print(feats.shape)
Or via the script: uv run MoonVit.py inference --onnx-model-path <this_dir> --image img.png.
How it was built (MoonVit.py)
uv run MoonVit.py build --output MoonVitOnnx # legacy exporter (default)
uv run MoonVit.py build --output MoonVitOnnx --dynamo # new torch.onnx dynamo exporter
uv run MoonVit.py eval --onnx-model-path MoonVitOnnx # original PyTorch vs ONNX
uv run MoonVit.py inference --onnx-model-path MoonVitOnnx --image img.png
Both exporters are supported and produce identical accuracy (cos 1.000000):
| Exporter | Graph | Weights | Notes |
|---|---|---|---|
legacy (default) |
~5,050 nodes | inline (~1.7 GB single file) | unrolls the data-dependent loops against the baked grid |
dynamo (--dynamo) |
~1,794 nodes | external .onnx.data |
cleaner/smaller graph; requires onnxscript |
Export notes (handled automatically by the script; both verified bit-faithful before export):
- Complex RoPE β real cos/sin. ONNX has no complex dtype; the 2D-RoPE
freqs_cisis precomputed for the baked grid and applied as real-valued rotation. PytorchGELUTanhshim β the upstream remote code imports it fromtransformers.activations(removed in newer transformers); it's exactlynn.GELU(approximate="tanh").- Dynamo only: full-attention SDPA (single baked image β the block-diagonal mask is all-True) and concrete-int patch-merger / pos-emb interpolation, to avoid unbacked-symint ops.
- Legacy only: int32βint64 normalization of
Slice/Gatherindex tensors.
Eval result
grid=28x28 original PyTorch vs ONNX
sample 0: cos=1.000000 max|Ξ|=9.995e-02
sample 1: cos=1.000000 max|Ξ|=6.195e-03
sample 2: cos=1.000000 max|Ξ|=2.052e-03
=== PASS (worst cosine 1.000000, tol 0.999) ===
(max|Ξ| is on large-magnitude features, feature std β 4.2 β cosine 1.0 is the real signal.)
Original model reference (PyTorch)
from PIL import Image
from transformers import AutoModel, AutoImageProcessor
model_path = "moonshotai/MoonViT-SO-400M"
model = AutoModel.from_pretrained(model_path, torch_dtype="auto", device_map="auto",
trust_remote_code=True)
processor = AutoImageProcessor.from_pretrained(model_path, trust_remote_code=True)
image = Image.open("./figures/demo.png")
proc = processor(image, return_tensors="pt").to(dtype=model.dtype, device=model.device)
image_features: list = model(proc.pixel_values, proc.image_grid_hws)
print(image_features[0].dtype, image_features[0].shape) # e.g. bf16, [N, 4, 1152]
See the Kimi-VL Technical Report for training details.
Model tree for Prince-1/MoonViT-SO-400M
Base model
moonshotai/MoonViT-SO-400M